all..
Iam new in react, I use react, redux and redux saga in my project. I just make simple api fetch using saga. but I don't know why the yield put command look did'nt work.
my code in bellow is my code:
types
export const GET_USERS_REQUESTED = "GET_USERS_REQUESTED";
export const GET_USERS_SUCCESS = "GET_USERS_SUCCESS";
export const GET_USERS_FAILED = "GET_USERS_FAILED";
actions
import * as type from "../types/users";
export function getUsers(users) {
return {
type: type.GET_USERS_REQUESTED,
payload: users,
};
};
reducers
import * as type from "../types/users";
const initialState = {
users: [],
loading: false,
error: null,
};
export default function users(state = initialState, action) {
switch (action.type) {
case type.GET_USERS_REQUESTED:
return {
...state,
loading: true,
};
case type.GET_USERS_SUCCESS:
return {
...state,
users: action.users,
loading: false,
};
case type.GET_USERS_FAILED:
return {
...state,
loading: false,
error: action.message,
};
default:
return state;
}
}
root reducers
import { combineReducers } from "redux";
import users from "./users";
const rootReducer = combineReducers({
users: users,
});
export default rootReducer;
user saga
import { call, put, takeEvery } from "redux-saga/effects";
import * as type from "../redux/types/users";
function getApi() {
return fetch("https://jsonplaceholder.typicode.com/users", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then((response) => {
response.json();
})
.catch((error) => {
throw error;
});
}
function* fetchUsers(action) {
try {
const users = yield call(getApi());
yield put({ type: type.GET_USERS_SUCCESS, users: users });
} catch (e) {
yield put({ type: type.GET_USERS_FAILED, message: e.message });
}
}
function* userSaga() {
yield takeEvery(type.GET_USERS_REQUESTED, fetchUsers);
}
export default userSaga;
root saga
import { all } from "redux-saga/effects";
import userSaga from "./users";
export default function* rootSaga() {
yield all([userSaga()]);
}
create store
import { createStore, applyMiddleware, compose } from "redux";
import rootReducer from "./reducers/index";
import createSagaMiddleware from "redux-saga";
import rootSaga from "../sagas/index";
const sagaMiddleware = createSagaMiddleware();
const store = compose(
window.devToolsExtension && window.devToolsExtension(),
applyMiddleware(sagaMiddleware)
)(createStore)(rootReducer);
sagaMiddleware.run(rootSaga);
export default store;
I don't know why my user saga looks like did'nt work. because loading state still have true value. Hopefully, anyone can help me.
Thanks in advance
You are calling your getApi and returning promise to the call itself. That call will do that for your. So just provide call with getApi like this:
...
function* fetchUsers(action) {
try {
const users = yield call(getApi); // <-- HERE IS THE CHANGE
yield put({ type: type.GET_USERS_SUCCESS, users: users });
} catch (e) {
yield put({ type: type.GET_USERS_FAILED, message: e.message });
}
}
...
Also you need to change your getApi since you are using fetch:
async function getApi() {
const result = await fetch('https://jsonplaceholder.typicode.com/users', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
})
.then((response) => {
response.json();
})
.catch((error) => {
throw error;
});
return result;
}
If you need to provide a variable to your getApi call you can just do:
const users = yield call(getApi, 'someValue');
And you getApi looks something like this:
async function getApi(someValue) {
const result = await fetch(`https://jsonplaceholder.typicode.com/users/${someValue}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
})
.then((response) => {
return response.json();
})
.catch((error) => {
throw error;
});
return result;
}
When you use call you should not call() it. Type yield call(getApi, "in case you have arguments"). You should do that, when you are waiting for a promise
Related
I am new in react native, i created some pages, which works fine, but some time i am getting this error
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method
i am using functional component, i am not able to identify why i am getting this issue ? if anyone can see my code and help me to resolve this issue, that will be the great, here i have added my whole code of it, thanks
JobDetailScreen.js
useEffect(() => {
let data = {}
data.user = login
data.job_id = route.params.job_id
dispatch(get_single_jobs(data));
},[]);
jobdetai.action.js
import * as types from "./jobdetail.type";
export const get_single_jobs = (data) => ({
type: types.GET_SINGLE_JOB,
payload: data,
});
jobdetail.type.js
export const GET_SINGLE_JOB = "GET_SINGLE_JOB";
jobdetail.saga.js
import { takeLatest, call, put,takeEvery } from "redux-saga/effects";
import SinglejobsServices from './jobdetail.services';
import JobsServices from '../jobs/jobs.services';
import * as types from './jobdetail.type';
import * as loadertypes from '../loader/loader.type';
function* get_single_jobs_service(payload) {
try {
yield put({ type: loadertypes.LOADERSTART});
const response = yield call(SinglejobsServices.list,payload);
if(response.status == false){
yield put({ type: types.SINGLE_JOBS_ERROR, response });
yield put({ type: loadertypes.LOADERSTOP});
}else{
yield put({ type: types.SET_SINGLE_JOB, response });
yield put({ type: loadertypes.LOADERSTOP});
}
} catch(error) {
let response = error.response.data;
yield put({ type: types.SINGLE_JOBS_ERROR, response });
yield put({ type: loadertypes.LOADERSTOP});
}
}
export default function* watchSinglejobs() {
yield takeEvery(types.GET_SINGLE_JOB, get_single_jobs_service);
}
Jobdetail.services.js
import axios from "axios";
import {API_URL} from '../../config/constant';
function services(){
const list = (request) => {
let req = request.payload;
const config = {
method: 'get',
url: API_URL + '/jobs/details/'+req.job_id,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${req.user.userToken}`
},
}
return axios(config)
.then(response => {
let result = response.data;
return result;
})
}
return {
list,
};
}
const SinglejobsServices = services();
export default SinglejobsServices;
Jobdetail.reducers.js
import * as types from "./jobdetail.type";
import singlejobsInitialState from "./jobdetail.initialstate";
const singleJobReducer = (state = singlejobsInitialState, action) => {
let response = action.response;
switch (action.type) {
case types.SET_SINGLE_JOB:
return {
...state,
data : response.data,
};
case types.SINGLE_JOBS_ERROR: {
return {
...state,
show: true,
msg: response.message,
};
}
case types.CLOSE_SINGLE_JOBS_MSG: {
return {
...state,
show: false,
msg: null,
};
}
default:
return state;
}
};
export default singleJobReducer;
JobdetailInitiate.js
const singlejobsInitialState = {
show : false,
msg : null,
data : []
};
export default singlejobsInitialState;
Jobdetail.controller.js
import React,{useState,useEffect} from 'react';
import JobDetails from './jobdetail'
import { useSelector } from "react-redux";
import {update_wishlist,apply_for_job} from '../jobs/jobs.action'
import {update_single_fav_job} from './jobdetail.action'
import { useDispatch } from "react-redux";
export default function JobDetailsController ({route,navigation}) {
// const jobs = useSelector((state) => state.jobs);
const singlejob = useSelector((state) => state.singlejob);
const login = useSelector((state) => state.login);
const dispatch = useDispatch();
const UpdateFav = (job_id) => {
let data = {};
data.job_id = job_id;
data.user = login;
dispatch(update_single_fav_job(data));
}
const ApplyForJob = (job_id) => {
let data = {};
data.home = false
data.job_id = job_id;
data.user = login;
dispatch(apply_for_job(data));
}
return (
<JobDetails navigation={navigation} job={singlejob.data} UpdateFav={UpdateFav} ApplyForJob={ApplyForJob} />
);
}
Redux Saga does not work after any error. It totally stops working and need to refresh the page. Working well when does not generate any error.
I want whether any error occurred the page should running if click the process again and fetch data from API.
If anyone gives any suggestion/solution, please.
My axios call as like below,
export async function getWithPagination(pageNumber, pageSize, searchObject) {
try {
const response = await axios.get(
uri.ApiBaseUrl + "Voucher/GetWithPagination",
{
params: {
...searchObject,
pageNumber,
pageSize,
},
}
);
return { response };
} catch (error) {
return { error };
}
}
export async function createVoucher(voucher) {
console.log("createVoucher service=> ", voucher);
try {
const response = await axios.post(uri.ApiBaseUrl + "Voucher", voucher, {});
return { response };
} catch (error) {
return { error };
}
}
The reducer, action, saga like below
import { put, takeLatest, call } from "redux-saga/effects";
import {
getWithPagination,
createVoucher
} from "../../services-crud/accounting/vouchersCrud";
export const reducer = (state = voucherState, action) => {
case actionTypes.VoucherGetRequested: {
return {
...state,
voucherGridObj: {
paging: {},
links: {},
lists: [],
},
isGridLoading: true,
};
}
case actionTypes.VoucherGetSucceed: {
return { ...state, voucherGridObj: action.data, isGridLoading: false };
}
case actionTypes.VoucherGetFailed: {
return { ...state, isGridLoading: false };
}
case actionTypes.VoucherCreateRequested: {
return { ...state };
}
case actionTypes.VoucherCreateSucceed: {
return {
...state,
voucherObj: { ...voucherInit },
voucherDetailsList: [],
voucherGridObj: {
...state.voucherGridObj,
lists: [{ ...action.data.voucher }, ...state.voucherGridObj.lists],
},
isSuccess: false,
isDataSyncNeeded: true,
};
}
case actionTypes.VoucherCreateFailed: {
return { ...state, isSuccess: false, isDataSyncNeeded: false };
}
}
export const actions = {
voucherGetRequested: (pageNumber, pageSize, searchObject) => ({
type: actionTypes.VoucherGetRequested,
pageNumber: pageNumber,
pageSize: pageSize,
searchObject: searchObject,
}),
voucherGetSucceed: (data) => ({
type: actionTypes.VoucherGetSucceed,
data: data,
}),
voucherGetFailed: () => ({ type: actionTypes.VoucherGetFailed }),
voucherCreate: (voucherObj, voucherImage, gurdianImage) => ({
type: actionTypes.VoucherCreate,
voucherObj: voucherObj,
voucherImage: voucherImage,
gurdianImage: gurdianImage,
}),
voucherCreateRequested: () => ({
type: actionTypes.VoucherCreateRequested,
}),
voucherCreateSucceed: (data) => ({
type: actionTypes.VoucherCreateSucceed,
data: data,
}),
voucherCreateFailed: () => ({
type: actionTypes.VoucherCreateFailed,
}),
};
export function* saga() {
try {
yield takeLatest(
actionTypes.VoucherGetRequested,
function* voucherRequested(action) {
const { response, error } = yield call(() =>
getWithPagination(
action.pageNumber,
action.pageSize,
action.searchObject
)
);
if (response) {
yield put(actions.voucherGetSucceed(response.data));
} else {
NotificationMessage(errorResponseProcess(error.response));
yield put(actions.voucherGetFailed(error));
}
}
);
} catch (error) {
NotificationMessage(errorResponseProcess(error.response));
yield put(actions.voucherGetFailed(error));
}
try {
yield takeLatest(
actionTypes.VoucherCreate,
function* createsvoucher(action) {
yield put(actions.voucherCreateRequested());
const { response, error } = yield call(() =>
createVoucher(
action.voucherObj,
action.voucherImage,
action.gurdianImage
)
);
if (response) {
NotificationMessage(response.data.notification);
yield put(actions.voucherCreateSucceed(response.data.data));
yield put(actions.voucherResetFlag());
} else {
NotificationMessage(errorResponseProcess(error.response));
yield put(actions.voucherCreateFailed(error));
}
}
);
} catch (error) {
NotificationMessage(errorResponseProcess(error.response));
yield put(actions.voucherCreateFailed(error));
}
}
The root reducer & store is below
import { all } from "redux-saga/effects";
import createSagaMiddleware from "redux-saga";
import { combineReducers, applyMiddleware, createStore } from "redux";
import * as accountsReportCrud from "./accounts-chart-duck/accountsReportCrudDuck";
import * as vouchersCrud from "./voucher-duck/voucherCrudDuck";
export const rootReducer = combineReducers({
accountsCrud: accountsCrud.reducer,
accountsReportCrud: accountsReportCrud.reducer,
});
export function* rootSaga() {
yield all([
accountsCrud.saga(),
accountsReportCrud.saga(),
...
]);
}
const sagaMiddleware = createSagaMiddleware();
const store = createStore(rootReducer, applyMiddleware(sagaMiddleware));
sagaMiddleware.run(rootSaga);
export default store;
In your root saga, you can spawn the tasks instead of all/fork so that one saga's error doesn't crash all the sagas.
Redux-saga docs on detached fork (spawn):
Uncaught errors from spawned tasks are not bubbled up to the parent.
I think this formation of sagas will does the trick
function* function_name({ payload }){
//code here
}
export function* function_name_sagas() {
yield takeEvery(action_type, function_name);
}
export default function* rootSaga() {
yield all([
fork(function_name_sagas),
]);
}
I am a 2-month front end developer.
I studied React at the same time as I joined the company,
so there are many parts I do not know well.
I am currently analyzing the code to proceed with code maintenance,
but there is an esoteric part of the code.
First, In the saga, There is no part where the action function is imported. (even in the root saga.)
So, Is it possible to implicitly import in the code?
I'm attaching some of the code to help you understand.
rootSaga.js
import { all } from "redux-saga/effects";
import watcherLogin from "store/sagas/login";
import watcherSignUp from "store/sagas/signup";
export default function* rootSaga() {
yield all([
watcherLogin(),
watcherSignUp(),
]);
}
watcherLogin() > index.js
export { default } from "./watcherLoginSaga"
watcherLogin() > watcherLoginSaga.js
import { all, put, fork, takeLatest } from "redux-saga/effects";
import Cookies from "universal-cookie";
import { fetchData } from "store/sagas/baseSaga";
function* onRequestLogin(action) {
const payload = action.payload;
const { history } = payload;
const successAction = (res) => {
const cookies = new Cookies();
cookies.set("hdmKey", res.data, {
path: "/",
maxAge: 3600,
});
return function* () {
const payload = res;
yield put({
type: "login/GET_USERINFO_REQUEST",
payload: {
method: "get",
api: "getUserInfo",
// token: res.data.key,
history,
},
});
yield put({
type: "login/LOGIN_REQUEST_SUCCESS",
payload,
});
yield put({
type: "base/IS_LOGGED",
payload,
});
yield history.push("/");
};
};
const failureAction = (res) => {
return function* () {
yield put({
type: "base/SHOW_MODAL",
payload: {
dark: true,
modalName: "alert",
modalContent: "login failure",
modalStyle: "purpleGradientStyle",
modalTitle: "Wait!",
},
});
};
};
yield fork(fetchData, payload, successAction, failureAction);
}
...
export default function* watcherLoginSaga() {
yield all([
takeLatest("login/LOGIN_REQUEST", onRequestLogin),
]);
}
loginModule > index.js
export { default } from "./loginModule";
loginModule > loginModule.js
import createReducer from "store/createReducer";
import { changeStateDeep } from "lib/commonFunction";
export const types = {
LOGIN_REQUEST: "login/LOGIN_REQUEST",
LOGIN_REQUEST_SUCCESS: "login/LOGIN_REQUEST_SUCCESS",
...
};
export const actions = {
loginRequest: (payload) => ({
type: types.LOGIN_REQUEST,
payload,
}),
...
};
const INITIAL_STATE = {
data: {
isLogged: false,
...
},
};
const reducer = createReducer(INITIAL_STATE, {
[types.ON_LOGIN]: (state, action) => {
state.data.isLogged = true;
},
[types.LOGIN_REQUEST_SUCCESS]: (state, action) => {
state.data.isLogged = true;
state.data.key = action.payload?.key || "key";
},
...
});
export default reducer;
I would appreciate it if you could help even a little.
An action is just a plain javascript object with a property type. As a convention (but not a strict requirement), action objects store their data in an optional property payload.
An actionCreator is a function which takes 0 to many arguments and uses them to create an action object.
What you are seeing in the code that you posted works, but it's not ideal. I'm guessing that certain improvements were made to the loginModule.js file and those improvements were not brought over to the watcherLoginSaga.js file.
Your types object allows you to use a variable such as types.LOGIN_REQUEST rather than the raw string "login/LOGIN_REQUEST". This has a bunch of benefits:
you get intellisense support in your IDE for autocomplete, etc.
you don't have to worry about making typos
you can change the value of the underlying raw string and you just need to change it one place
That last one is critical, because if you were to change the value of types.LOGIN_REQUEST to anything other than "login/LOGIN_REQUEST" right now your sagas would stop working because they are using the raw string. So you are absolutely right in thinking that the saga should import from the actions. I recommend that you import your types and replace the strings with their corresponding variables.
The same situation is happening with the actions that the saga is dispatching via put.
What's going on in this code is that the saga is creating a raw action object itself from the type and payload rather than creating it though an action creator function. That's fine but it's not great. Like the types, action creators are a level of abstraction that allows for better code maintenance. You could definitely extract the logic from your saga into action creator functions, if they don't exist already.
For example, your base module could include:
const types = {
IS_LOGGED: "base/IS_LOGGED",
SHOW_MODAL: "base/SHOW_MODAL"
};
export const actions = {
isLogged: (payload) => ({
type: types.IS_LOGGED,
payload
}),
showLoginFailure: () => ({
type: types.SHOW_MODAL,
payload: {
dark: true,
modalName: "alert",
modalContent: "login failure",
modalStyle: "purpleGradientStyle",
modalTitle: "Wait!"
}
})
};
And you can move the logic for creating actions away from your saga.
import { all, put, fork, takeLatest } from "redux-saga/effects";
import Cookies from "universal-cookie";
import { fetchData } from "store/sagas/baseSaga";
import {actions as loginActions, types as loginTypes} from "../some_path/loginModule";
import {actions as baseActions} from "../some_path/baseModule";
function* onRequestLogin(action) {
const payload = action.payload;
const { history } = payload;
const successAction = (res) => {
const cookies = new Cookies();
cookies.set("hdmKey", res.data, {
path: "/",
maxAge: 3600,
});
return function* () {
const payload = res;
yield put(loginActions.getUserInfoSuccess(history));
yield put(loginActions.loginSuccess(payload));
yield put(baseActions.isLogged(payload));
yield history.push("/");
};
};
const failureAction = (res) => {
return function* () {
yield put(baseActions.showLoginFailure());
};
};
yield fork(fetchData, payload, successAction, failureAction);
}
export default function* watcherLoginSaga() {
yield all([
takeLatest(loginTypes.LOGIN_REQUEST, onRequestLogin),
]);
}
I have created an authentification system in react with redux and axios but I can`t figure out how to render the data in my components.
This is my actions/auth.js:
import axios from 'axios';
import {
SIGNUP_SUCCESS,
SIGNUP_FAIL,
LOGIN_SUCCESS,
LOGIN_FAIL,
ACTIVATION_SUCCESS,
ACTIVATION_FAIL,
RESET_PASSWORD_SUCCESS,
RESET_PASSWORD_FAIL,
RESET_PASSWORD_CONFIRM_SUCCESS,
RESET_PASSWORD_CONFIRM_FAIL,
LOGOUT,
USER_LOADED_SUCCESS,
USER_LOADED_FAIL,
AUTHENTICATED_FAIL,
AUTHENTICATED_SUCCESS
} from './types';
export const checkAuthenticated = () => async dispatch => {
if (typeof window == 'undefined') {
dispatch({
type: AUTHENTICATED_FAIL
});
}
if (localStorage.getItem('access')) {
const config = {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ token: localStorage.getItem('access') });
try {
const res = await axios.post(`${process.env.REACT_APP_API_URL}/auth/jwt/verify/`, body, config);
if (res.data.code !== 'token_not_valid') {
dispatch({
type: AUTHENTICATED_SUCCESS
});
} else {
dispatch({
type: AUTHENTICATED_FAIL
});
}
} catch (err) {
dispatch({
type: AUTHENTICATED_FAIL
});
}
} else {
dispatch({
type: AUTHENTICATED_FAIL
});
}
};
export const load_user = () => async dispatch => {
if (localStorage.getItem('access')) {
const config = {
headers: {
'Content-Type': 'application/json',
'Authorization': `JWT ${localStorage.getItem('access')}`,
'Accept': 'application/json'
}
};
try {
const res = await axios.get(`${process.env.REACT_APP_API_URL}/auth/users/me/`, config);
dispatch({
type: USER_LOADED_SUCCESS,
payload: res.data
});
} catch (err) {
dispatch({
type: USER_LOADED_FAIL
});
}
} else {
dispatch({
type: USER_LOADED_FAIL
});
}
};
export const login = (email, password) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ email, password });
try {
const res = await axios.post(`${process.env.REACT_APP_API_URL}/auth/jwt/create/`, body, config);
dispatch({
type: LOGIN_SUCCESS,
payload: res.data
});
dispatch(load_user());
} catch (err) {
dispatch({
type: LOGIN_FAIL
});
}
};
export const logout = () => dispatch => {
dispatch({ type: LOGOUT });
};
This is my reducers/auth.js:
import {
SIGNUP_SUCCESS,
SIGNUP_FAIL,
LOGIN_SUCCESS,
LOGIN_FAIL,
LOGOUT,
AUTHENTICATED_FAIL,
AUTHENTICATED_SUCCESS,
USER_LOADED_SUCCESS,
USER_LOADED_FAIL
} from '../actions/types';
const initialState = {
access: localStorage.getItem('access'),
refresh: localStorage.getItem('refresh'),
isAuthenticated: null,
user: null
};
export default function(state = initialState, action) {
const { type, payload } = action;
switch(type) {
case AUTHENTICATED_SUCCESS:
return {
...state,
isAuthenticated: true
}
case LOGIN_SUCCESS:
localStorage.setItem('access', payload.access);
return {
...state,
isAuthenticated: true,
access: payload.access,
refresh: payload.refresh
}
case USER_LOADED_SUCCESS:
return {
...state,
user: payload
}
case AUTHENTICATED_FAIL:
return {
...state,
isAuthenticated: false
}
case USER_LOADED_FAIL:
return {
...state,
user: null
}
case LOGIN_FAIL:
case LOGOUT:
localStorage.removeItem('access');
localStorage.removeItem('refresh');
return{
...state,
access: null,
refresh: null,
isAuthenticated: false,
user: null
}
default:
return state
}
}
If I log in and use the redux Devtool I can see this state:
{
auth: {
access: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjAzNDgzODY1LCJqdGkiOiJhYTAzYzIzNTUwN2M0YTkxYjA2NjNmNDc0ZTU2MjIxMSIsInVzZXJfaWQiOjF9.Jyld4U7i6EqmsNoi0_qT9O9Kcu1TiEuyLLYCWWaoBrU',
refresh: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTYwMzU2OTk2NSwianRpIjoiOWIzMWIyN2M1ODkyNDRiZDk3Y2EwMDI1NTY2Mzk3ZWMiLCJ1c2VyX2lkIjoxfQ.UgH_753OoWD3NXiwPwa1645_vIHUl-FwyvQMJWMgHtk',
isAuthenticated: true,
user: {
name: 'Jonas Levin',
id: 1,
email: 'jonaslevin1903#gmail.com'
}
}
}
But I can`t figure out how to display the data, for example user.name.
I already tried to use mapStateToProps in one of my components but I get the error: "TypeError: Cannot read property 'name' of undefined"
const mapStateToProps = state => ({
userName: state.user.name,
userEmail: state.user.email
});
Edit
This is the response data that I get. But as you can see there is another API call which is still from the login page where I was on before I got redirected to '/' and that light red /me call has an error message in it because when your on the login page you don`t have an access token.
How can I access this response data in my Components to render the Name?
Store.js:
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const initialState = {};
const middleware = [thunk];
const store = createStore(
rootReducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
I managed to access the username in my layout.js file by adding the state to the props:
const mapStateToProps = (state, ownProps) => {
return {
isAuthenticated: state.auth.isAuthenticated,
user: state.auth.user,
props: ownProps
}
};
I used ownProps to be able to also use props.children in the layout container. Than I gave tham as parameters to the layout container and was able to access the username with user.name.
I´m not entirely sure why it worked now and not before when I already tried to use mapStateToProps.
this is how you should do it access auth reducer then th user
const mapStateToProps = state => ({
userName: state.auth.user.name,
userEmail: state.auth.user.email
});
this is how redux works , let's say this is your store
import {cartReducer} from './reducers/CartReducer'
import { authReducer } from './reducers/AuthReducer'
import { ordersReducer } from './reducers/OrdersReducer'
import { errorsReducer } from './reducers/ErrorsReducer'
const initialState={
products:{
items:[],
filterdProducts:[]
},
cart:{
items:[],
},
orders:{
items:[],
canOrder:true,
},
auth: {
access: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjAzNDgzODY1LCJqdGkiOiJhYTAzYzIzNTUwN2M0YTkxYjA2NjNmNDc0ZTU2MjIxMSIsInVzZXJfaWQiOjF9.Jyld4U7i6EqmsNoi0_qT9O9Kcu1TiEuyLLYCWWaoBrU',
refresh: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTYwMzU2OTk2NSwianRpIjoiOWIzMWIyN2M1ODkyNDRiZDk3Y2EwMDI1NTY2Mzk3ZWMiLCJ1c2VyX2lkIjoxfQ.UgH_753OoWD3NXiwPwa1645_vIHUl-FwyvQMJWMgHtk',
isAuthenticated: true,
user: {
name: 'Jonas Levin',
id: 1,
email: 'jonaslevin1903#gmail.com'
}
},
error:{
msg:null,
status:null,
id:null
}
}
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore(combineReducers({
products: productsReducer,
cart : cartReducer ,
orders : ordersReducer ,
auth : authReducer,
error : errorsReducer ,
}),
initialState,
composeEnhancer(applyMiddleware(thunk))
)
export default store
if you want to access user from any other component you'mm need to access auth reducer first , same for items you can either access products.items or cart .items and so on
If you use a functional component, you could use useSelector hook.
const user = useSelector(state => state.auth.user)
I've got this working but i'm after a more 'best practice way'.
its using the https://icanhazdadjoke api to display a random joke that gets updated every x seconds. is there a better way of doing this?
eventually i want to add stop, start, reset functionality and feel this way might not be the best.
Any middleware i can use?
Redux actions
// action types
import axios from 'axios';
export const FETCH_JOKE = 'FETCH_JOKE';
export const FETCH_JOKE_SUCCESS = 'FETCH_JOKE_SUCCESS';
export const FETCH_JOKE_FAILURE = 'FETCH_JOKE_FAILURE';
function fetchJoke() {
return {
type: FETCH_JOKE
};
}
function fetchJokeSuccess(data) {
return {
type: FETCH_JOKE_SUCCESS,
data
};
}
function fetchJokeFail(error) {
return {
type: FETCH_JOKE_FAILURE,
error
};
}
export function fetchJokeCall(){
return function(dispatch){
dispatch(fetchJoke());
return axios.get('https://icanhazdadjoke.com', { headers: { 'Accept': 'application/json' }})
.then(function(result){
dispatch(fetchJokeSuccess(result.data))
})
.catch(error => dispatch(fetchJokeFail(error)));
}
}
Redux reducer
import {combineReducers} from 'redux';
import {FETCH_JOKE, FETCH_JOKE_SUCCESS, FETCH_JOKE_FAILURE} from '../actions';
const defaultStateList = {
isFetching: false,
items:[],
error:{}
};
const joke = (state = defaultStateList, action) => {
switch (action.type){
case FETCH_JOKE:
return {...state, isFetching:true};
case FETCH_JOKE_SUCCESS:
return {...state, isFetching:false, items:action.data};
case FETCH_JOKE_FAILURE:
return {...state, isFetching:false, error:action.data};
default:
return state;
}
};
const rootReducer = combineReducers({
joke
});
export default rootReducer;
Joke component
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { fetchJokeCall } from '../actions';
class Joke extends Component {
componentDidMount() {
this.timer = setInterval(()=> this.props.fetchJokeCall(), 1000);
}
componentWillUnmount() {
clearInterval(this.timer)
this.timer = null;
}
render() {
return (
<div>
{this.props.joke.joke}
</div>
);
}
}
Joke.propTypes = {
fetchJokeCall: PropTypes.func,
joke: PropTypes.array.isRequired
};
function mapStateToProps(state) {
return {
joke: state.joke.items,
isfetching: state.joke.isFetching
};
}
export default connect(mapStateToProps, { fetchJokeCall })(Joke);
Redux-Sagas is better and we are using it in our applications as well, this is how you can poll using Redux-Sagas
Just to give you an idea this is how you can do it, You also need to understand how Redux-Sagas work
Action
export const FETCH_JOKE = 'FETCH_JOKE';
export const FETCH_JOKE_SUCCESS = 'FETCH_JOKE_SUCCESS';
export const FETCH_JOKE_FAILURE = 'FETCH_JOKE_FAILURE';
export const START_POLLING = 'START_POLLING';
export const STOP_POLLING = 'STOP_POLLING';
function startPolling() {
return {
type: START_POLLING
};
}
function stopPolling() {
return {
type: STOP_POLLING
};
}
function fetchJoke() {
return {
type: FETCH_JOKE
};
}
function fetchJokeSuccess(data) {
return {
type: FETCH_JOKE_SUCCESS,
data
};
}
function fetchJokeFail(error) {
return {
type: FETCH_JOKE_FAILURE,
error
};
}
Reducer
import {combineReducers} from 'redux';
import {FETCH_JOKE, FETCH_JOKE_SUCCESS, FETCH_JOKE_FAILURE, START_POLLING, STOP_POLLING } from '../actions';
const defaultStateList = {
isFetching: false,
items:[],
error:{},
isPolling: false,
};
const joke = (state = defaultStateList, action) => {
switch (action.type){
case FETCH_JOKE:
return {...state, isFetching:true};
case FETCH_JOKE_SUCCESS:
return {...state, isFetching:false, items:action.data};
case FETCH_JOKE_FAILURE:
return {...state, isFetching:false, error:action.data};
case START_POLLING:
return {...state, isPolling: true};
case STOP_POLLING:
return {...state, isPolling: false};
default:
return state;
}
};
const rootReducer = combineReducers({
joke
});
export default rootReducer;
Sagas
import { call, put, takeEvery, takeLatest, take, race } from 'redux-saga/effects'
import {FETCH_JOKE, FETCH_JOKE_SUCCESS, FETCH_JOKE_FAILURE, START_POLLING, STOP_POLLING } from '../actions';
import axios from 'axios';
function delay(duration) {
const promise = new Promise(resolve => {
setTimeout(() => resolve(true), duration)
})
return promise
}
function* fetchJokes(action) {
while (true) {
try {
const { data } = yield call(() => axios({ url: ENDPOINT }))
yield put({ type: FETCH_JOKE_SUCCESS, data: data })
yield call(delay, 5000)
} catch (e) {
yield put({ type: FETCH_JOKE_FAILURE, message: e.message })
}
}
}
function* watchPollJokesSaga() {
while (true) {
const data = yield take(START_POLLING)
yield race([call(fetchJokes, data), take(STOP_POLLING)])
}
}
export default function* root() {
yield [watchPollJokesSaga()]
}
You can also use Redux-Observable, if you want to get more into this read this article
I've been working on pretty much the same problem, except that I wasn't concerned about starting and stopping the poll. For some reason the while loop kept freezing my app so I dispensed of it and instead set up my saga like this.
import { all, takeLatest, call, put } from 'redux-saga/effects';
import axios from 'axios';
import { API_CALL_REQUEST, API_CALL_SUCCESS, API_CALL_FAILURE, API_CALL_FETCHED } from
'../actions/giphy';
function apiFetch() {
let randomWord = require('random-words');
let API_ENDPOINT = `https://api.giphy.com/v1/gifs/search?
api_key=MYKEY&q=${randomWord()}&limit=12`;
return axios({
method: "get",
url: API_ENDPOINT
});
}
export function* fetchImages() {
try {
const res = yield call(apiFetch)
const images = yield res.data
yield put({type: API_CALL_SUCCESS, images})
} catch (e) {
yield put({type: API_CALL_FAILURE, e})
console.log('Error fetching giphy data')
}
}
export default function* giphySaga() {
yield all([
takeLatest(API_CALL_REQUEST, fetchImages),
]);
}
Then inside my component I added this.
componentDidMount() {
this.interval = setInterval(() => {
this.props.dispatch({type: 'API_CALL_REQUEST'});
}, 5000);
}
componentWillUnmount() {
clearInterval(this.interval)
}
It's working, but would like some feedback on how this could be possibly improved.
Here's a poor man's way. I don't think it's the best way but it doesn't require any extra library.
Actions
// action types
import axios from 'axios';
export const START_POLLING_JOKE = 'START_POLLING_JOKE';
export const STOP_POLLING_JOKE = 'STOP_POLLING_JOKE';
export const FETCH_JOKE = 'FETCH_JOKE';
export const FETCH_JOKE_SUCCESS = 'FETCH_JOKE_SUCCESS';
export const FETCH_JOKE_FAILURE = 'FETCH_JOKE_FAILURE';
const defaultPollingInterval = 60000
function startPollingJoke(interval = defaultPollingInterval) {
return function (dispatch) {
const fetch = () => dispatch(fetchJoke())
dispatch({
type: START_POLLING_JOKE,
interval,
fetch,
})
}
}
function stopPollingJoke() {
return {
type: STOP_POLLING_JOKE
}
}
function fetchJoke() {
return {
type: FETCH_JOKE
};
}
function fetchJokeSuccess(data) {
return {
type: FETCH_JOKE_SUCCESS,
data
};
}
function fetchJokeFail(error) {
return {
type: FETCH_JOKE_FAILURE,
error
};
}
export function pollJokeCall(interval = defaultPollingInterval) {
return function (dispatch) {
dispatch(fetchJoke())
dispatch(startPollingJoke(interval))
}
}
export function fetchJokeCall() {
return function(dispatch){
dispatch(fetchJoke());
return axios.get('https://icanhazdadjoke.com', { headers: { 'Accept': 'application/json' }})
.then(function(result){
dispatch(fetchJokeSuccess(result.data))
})
.catch(error => dispatch(fetchJokeFail(error)));
}
}
Reducers
import {combineReducers} from 'redux';
import {
START_POLLING_JOKE,
STOP_POLLING_JOKE,
FETCH_JOKE,
FETCH_JOKE_SUCCESS,
FETCH_JOKE_FAILURE,
} from '../actions';
const defaultStateList = {
isFetching: false,
items:[],
error:{},
pollingId: null,
polling: false,
};
const joke = (state = defaultStateList, action) => {
switch (action.type){
case START_POLLING_JOKE:
clearInterval(state.pollingId)
return {
...state,
polling: true,
pollingId: setInterval(action.fetch, action.interval),
}
}
case STOP_POLLING_JOKE:
clearInterval(state.pollingId)
return {...state, polling: false, pollingId: null}
case FETCH_JOKE:
return {...state, isFetching:true};
case FETCH_JOKE_SUCCESS:
return {...state, isFetching:false, items:action.data};
case FETCH_JOKE_FAILURE:
return {...state, isFetching:false, error:action.data};
default:
return state;
}
};
const rootReducer = combineReducers({
joke
});
export default rootReducer;
Component (might have a bug because I'm not used to class components)
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { pollJokeCall, stopPollingJoke } from '../actions';
class Joke extends Component {
componentDidMount() {
this.props.pollJokeCall()
}
componentWillUnmount() {
this.props.stopPollingJoke()
}
render() {
return (
<div>
{this.props.joke.joke}
</div>
);
}
}
Joke.propTypes = {
pollJokeCall: PropTypes.func,
stopPollingJoke: PropTypes.func,
joke: PropTypes.array.isRequired,
};
function mapStateToProps(state) {
return {
joke: state.joke.items,
isfetching: state.joke.isFetching
};
}
export default connect(mapStateToProps, { pollJokeCall, stopPollingJoke })(Joke);
I have made a small (5kb gzipped) helper to create polling based on redux-thunk store. The idea is to have a logic to prevent registering the same polling twice, have callbacks between iterations and more.
https://www.npmjs.com/package/redux-polling-thunk
redux-saga is great and I've been using this with redux. It provides a great api to do things like delay, polling, throttling, race conditions, task cancellations. So using redux-saga, you can add a watcher whcih will keep on pooling
function* pollSagaWorker(action) {
while (true) {
try {
const { data } = yield call(() => axios({ url: ENDPOINT }));
yield put(getDataSuccessAction(data));
yield call(delay, 4000);
} catch (err) {
yield put(getDataFailureAction(err));
}
}
}