redux dispatch resets other states to default - javascript

Summary
I have 2 states: isLogged and counter. isLogged is a bool where the user can set it to true or false, in doing so the user can access certain pages where if isLogged is set to true. The counter is just a simple int where the user can increment/decrement it. Both states are saved on sessionStorage if their values are changed; and on init, I ran a dispatch to get both values from the sessionstorage. The default value for isLogged is false, and counter is 0.
The Problem
Let's say for example we have this state values:
isLogged = true
counter = 4
If I try to call an increment action, the values will now be this:
isLogged = false
counter = 5
As you can see, it resets the isLogged to its default value. However when I look on the sessionstorage panel on Chrome, it says there: {isLogged: true, counter: 5} so it is still saving the values on the sessonstorage and everything there seems to work fine. However on the app itself, it says that isLogged is now false and the user cannot access certain pages anymore even if he didn't logout for some reason.
What's weird is if I refresh the page, the states are now:
isLogged = true
counter = 5
(of course the data have been fetched from the sessionstorage) But if I now switch the isLogged to false, it will also reset the counter to 0:
isLogged = false
counter = 0
So the problem is when I call a dispatch/action to modify a certain state, all other states get reset.
Codes
src/actions/index.js
export const increment = (num) => {
return {
type: 'INCREMENT',
payload: num
}
}
export const decrement = () => {
return {
type: 'DECREMENT'
}
}
export const get_data = () => {
return {
type: 'GET'
}
}
export const signin = () => {
return {
type: 'SIGN_IN'
}
}
export const signout = () => {
return {
type: 'SIGN_OUT'
}
}
export const getlogged = () => {
return {
type: 'GET'
}
}
src/reducers/counter.js
const counterReducer = (state = 0, action) => {
switch(action.type) {
case 'INCREMENT':
state += action.payload
saveCounter(state)
return state
case 'DECREMENT':
state -= 1
saveCounter(state)
return state
case 'GET':
state = getCounter()
return state
default:
return 0
}
}
const saveCounter = (state) => {
const data = {
counter: state
}
sessionStorage.setItem("data", JSON.stringify(data))
}
const getCounter = () => {
if (sessionStorage.getItem("data") != null) {
const data = JSON.parse(sessionStorage.getItem("data"))
return data["counter"]
}
else {
return 0
}
}
export default counterReducer
src/reducers/isLogged.js
const loggedReducer = (state = false, action) => {
switch(action.type) {
case 'SIGN_IN':
state = true
saveLogged(state)
return state
case 'SIGN_OUT':
state = false
saveLogged(state)
return state
case 'GET':
state = getLogged()
return state
default:
return false
}
}
const saveLogged = (state) => {
const data = {isLogged: state}
sessionStorage.setItem("logged", JSON.stringify(data))
}
const getLogged = () => {
if (sessionStorage.getItem("logged") != null) {
const data = JSON.parse(sessionStorage.getItem("logged"))
return data["isLogged"]
}
else {
return false
}
}
export default loggedReducer
I'm calling it on a component like this:
useEffect(() => {
getCounter()
}, [])
const counter = useSelector(state => state.counter)
const dispatch = useDispatch()
const getCounter = () => {
dispatch(get_data())
}
return (
<div className="container-fluid mt-3">
<p className="text-white">Counter: {counter}</p>
<button onClick={() => dispatch(increment(4))} className="btn btn-primary">+</button>
<button onClick={() => dispatch(decrement())} className="btn btn-danger">-</button>
</div>
)

The problem is that in both your reducers, you're returning the default state in the default case. This means that every non handled action will reset the states.
For instance, when you dispatch an increment action, it goes in the default state in your reducer and sets the state to false.
The default case should simply return state unchanged.
Besides that, you're useEffect seems a bit dangerous, as getCounter changes at every render, it will be called every time.
I would also advise you to use a middleware for redux if you want to save something in the localStorage. A reducer is supposed to be a function with no side-effects so you're not respecting the rule here.
You should also not read from the localStorage via an action but instead read from the localStorage when creating your store.

Related

Redux: dispatching an action multiple times results to too many api requests

Using an api for anime called Jikan, I'm trying to display promo thumbnails of new anime shows.
I'm using two api calls, one to get the new anime shows:
export const get_new_anime = () =>
`${base_url}search/anime?q&order_by=score&status=airing&sort=desc`;
and one for getting the videos (containing promos) of anime by getting its id.
export const get_news = (anime_id) => `${base_url}anime/${anime_id}/videos`;
In my home page, here I am mapping the shows, returning a component for each anime:
<Promos>
{new.map((anime, index) => (
<Anime key={anime.mal_id} index={index}></Anime>))}
</Promos>
And for each Anime component, I have a useEffect which uses useDispatch for every new id
const Anime = ({ id, index }) => {
const dispatch = useDispatch();
const loadDetailHandler = () => {
// eslint-disable-line react-hooks/exhaustive-deps
dispatch(loadDetail(id));
useEffect(() => {
loadDetailHandler(id);
}, [id]); // eslint-disable-line react-hooks/exhaustive-deps
const promo = useSelector((state) => state.detail.promo);
const isLoading = useSelector((state) => state.detail.isLoading);
return (
<PromoBox
style={
!isLoading
? { backgroundImage: `url("${promo[index][0].image_url}")` }
: null
}
></PromoBox>);
};
Here is how my promoReducer looks like:
const initState = {
promo: [],
isLoading: true,
};
const promoReducer = (state = initState, action) => {
switch (action.type) {
case "LOADING_PROMO":
return {
...state,
isLoading: true,
};
case "GET_DETAIL":
return {
...state,
promo: [...state.promo, action.payload.promo],
isLoading: false,
};
default:
return { ...state };
}
};
export default promoReducer;
and here is the promoAction:
export const loadPromo = (id) => async (dispatch) => {
dispatch({
type: "LOADING_PROMO",
});
const promoData = await axios.get(get_promos(id));
dispatch({
type: "GET_DETAIL",
payload: {
promo: promoData.data.promo,
},
});
};
While it does return the promo data as the action is dispatched, the problem is that in some instances of dispatching, no data is returned. Here is a screenshot from redux devtools to show what I mean:
and I was trying to get the promos of all the new anime, in which I was expecting to get 50 results of promo data. In devtools, you can see I only got 9 of them. This is followed by an error 429 (too many requests):
How can I resolve this issue? And is there a better way to do this, because this seems like bad practice:
Well it seems that you're limited by the api itself and it's threshold for the number of request per unit of time. There should probably be a request that allows you to pass multiple anime ids to get request in order to avoid requesting details for each anime individually.

useSelector() return undefined

I just learn react-native basic, and when work with redux, i have problem with useSelector , here is some of my code
Here is store component
//store.js
initState = {
loginStatus: false,
}
const LoginAction = (state = {initState}, action) => {
if (action.type == 'changeLogin') {
return { loginStatus:!state.loginStatus }
}
return state
}
const store = createStore(LoginAction, composeWithDevTools());
export default store
Here is Login Function
function LoginScreen({ navigation, props }) {
const dispatch = useDispatch()
const Login = useSelector(state => {
return state.LoginStatus
})
function getLogin() {
return Login
}
function handleLogin() {
dispatch({ type: 'changeLogin' })
}
console.log('Login ' + Login) // it return undefined
I have tried this method useSelector state returns undefined (React-Redux) but it didn't work!
Here is screenshot of what happened
But when i add that to login button, it return true, then continute to undefined
<Formik
validateOnMount
validationSchema={loginValidationSchema}
initialValues={{ email: '', password: '' }}
onSubmit={
() => {
handleLogin()
console.log('When submit ' + Login) // true then undefined
SetTimer();
}
// () => navigation.navigate('Login')
}
>
Please help , thank a lot
The casing is wrong in your selector. It should be return state.loginStatus. Also, your LoginAction is technically a reducer, not an action.
const Login = useSelector(state => {
return state.loginStatus
})
Edit: An additional issue in the reducer is the initial state has initState as the top-level key in the object, when the intent is just for it to be assigned directly:
const LoginAction = (state = initState, action) = {
// reducer code here
}

Update state props without firing a dispatch() - React Redux

I have a modal containing a button that fires a HTTP request, at which point the displayed html will change depending on a successful/error response from the server, where the response changes a state prop that is dealt with in the mapStatesToProps function.
The issue I have now is that I am wanting to reset the modal to its initial state pre-request when I close it.
I had previously done this by using local component state but have since updated the functionality to use the request mapped state props shown above.
I am curious if it possible to reset the state without firing a dispatch to a random URI?
Component.jsx
const mapStatesToProps = ({myState}) => ({
response: myState.response,
success: !!(myState.success),
fail: !!(myState.fail)
});
const mapDispatchToProps = dispatch => ({
doReq: () => {
dispatch(doMyRequest());
}
});
class MyComponent extends Component {
toggleModal = () => // modal toggle code
render() {
const {response, success, fail} = this.props;
<div className="myModal">
// Modal stuff here
{!success && !fail && (
<button onClick="() => toggleModal()">Close modal</button>
)}
{success && !fail && (
<h1>Some success message</h1>
)}
{!success && fail && (
<h1>Some fail message</h1>
)}
</div>
}
}
req-actions.js
export const MY_REQUEST;
export const MY_REQUEST_SUCCESS;
export const MY_REQUEST_ERROR;
export const doMyRequest = () => ({
type: MY_REQUEST,
agent: agent.req.doRequest
})
req-reducer.js
import { deepEqual, deepClone } from '../McUtils';
import {
MY_REQUEST,
MY_REQUEST_ERROR,
MY_REQUEST_SUCCESS
} from "../actions/req-actions";
export default (state = {}, action) => {
let newState = deepClone(state);
switch (action.type) {
case MY_REQUEST:
console.log('SENDING REQUEST');
newState.response = null;
newState.success = false;
newState.fail = false;
break;
case MY_REQUEST_SUCCESS:
console.log('SUCCESS');
newState.response = action.payload;
newState.success = true;
newState.fail = false;
break;
case MY_REQUEST_ERROR:
console.log('FAIL');
newState.response = action.payload;
newState.success = false;
newState.fail = true;
break;
default:
return state;
}
return newState;
}
Just use another action:
case MY_REQUEST_RESET:
return {} // only putting {} in here because this is what you have defined your initialState to be according to your reducer.
Personal preference is to clearly define your initial state like this.
const initialState = {};
export default (state = initialState, action) => {
switch(action.type) {
... your existing handlers
case MY_REQUEST_RESET:
return initialState
}
}
Wiring it up:
const mapDispatchToProps = dispatch => ({
doReq: () => {
dispatch(doMyRequest()),
},
reset: () => {
dispatch(resetMyRequest());
}
});
// types
const MY_REQUEST_RESET = 'MY_REQUEST_RESET';
// action creator (may be referred to as "actions")
const resetMyRequest = () => ({ type: MY_REQUEST_RESET })
EDIT: While I'm here, this is really gross:
let newState = deepClone(state);
and reeks of "I don't really know what I'm doing" and can lead to performance issues. You are deepCloning the state on every action fired through redux, even if the actions aren't one's this reducer is interested in.
If you are changing the state in the reducer, just change the part you are concerned with, don't change "all" of it.
e.g.
export default (state = {}, action) => {
switch (action.type) {
case MY_REQUEST:
console.log('SENDING REQUEST');
return {
success: false,
fail: false,
response: null
}
case MY_REQUEST_SUCCESS:
console.log('SUCCESS');
return {
...state, // this will contain "fail: false" already
success: true,
response: action.payload
};
case MY_REQUEST_ERROR:
console.log('FAIL');
return {
...state, // this will contain "success: false" already
error: true,
response: action.payload
};
default:
return state;
}

React Hooks useState Array empty with states rendered in the component

I have a situation where i can successfully dispatch my states with reducers and i can render it in my component
Here the relevant code
in my action/index.js
export const receivedLeaguesList = json => ({
type: RECEIVE_LEAGUES_LIST,
json: json
});
export function fetchLeaguesList() {
return function(dispatch) {
dispatch(requestLeaguesList());
return axios
.get("https://www.api-football.com/demo/v2/leagues/")
.then(res => {
let leagues = res.data.api.leagues;
dispatch(receivedLeaguesList(leagues));
})
.catch(e => {
console.log(e);
});
}
}
my reducers/index.js
import { REQUEST_LEAGUES_LIST, RECEIVE_LEAGUES_LIST } from "../actions";
const initialState = {
leaguesList: [],
isLeagueListLoading: false
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case REQUEST_LEAGUES_LIST:
return { ...state, isLeagueListLoading: true };
case RECEIVE_LEAGUES_LIST:
return { ...state, leaguesList: action.json, isLeagueListLoading: false };
default:
return state;
}
};
in my component component/Leagues.js
let Leagues = ({ leaguesList, loading, getList }) => {
useEffect(() => {
getList();
}, [getList]);
const [itemsLeagues] = useState([leaguesList]);
console.log("league list", itemsLeagues);
const mapDispatchToProps = {
getList: fetchLeaguesList
};
I have reproduced the demo here => https://codesandbox.io/s/select-demo-71u7h?
I can render my leaguesList states in my component doing the map, but why when
const [itemsLeagues] = useState([leaguesList]);
console.log("league list", itemsLeagues);
returns an empty array ?
See the image
You're setting useState's init value wrong:
const [itemsLeagues] = useState(leaguesList);
instead of
const [itemsLeagues] = useState([leaguesList]);
The return value of useState isn't the value itself, but the array of value and mutator:
const [value, setValue] = useState([42, 43])
// here's value equals [42, 43]
So if you were trying to destructure the wrapping array you passed to useState(), you should use it like this (though you don't need it):
const [[itemsLeagues]] = useState([leaguesList]);

Losing Local Storage on Page Refresh in React/Redux

I'm using React and Redux and storing data in a loggedUser variable upon user login.
my login reducer looks like this:
const loginReducer = (state = null, action) => {
switch (action.type) {
case "SET_USER":
if (action.data) userService.setToken(action.data.token);
return action.data;
default:
return state;
}
};
export const fetchUser = () => {
return dispatch => {
const userStr = window.localStorage.getItem("loggedVintageUser");
const user = JSON.parse(userStr);
if (user) {
dispatch({ type: "SET_USER", data: user });
}
};
};
export const setUser = data => {
return dispatch => {
dispatch({ type: "SET_USER", data });
};
};
export const login = data => {
return async dispatch => {
const user = await loginService.login({
username: data.username,
password: data.password
});
window.localStorage.setItem("loggedVintageUser", JSON.stringify(user));
dispatch({ type: "SET_USER", data: user });
};
};
In my core App component i'm dispatching the fetchUser and setUser creators
useEffect(() => {
fetchUser();
}, [props.fetchUser]);
useEffect(() => {
const loggedUserJSON = window.localStorage.getItem("loggedVintageUser");
if (loggedUserJSON) {
const user = JSON.parse(loggedUserJSON);
props.setUser(user);
userService.setToken(user.token);
}
}, []);
I'm displaying a list of favorite items for a user and when i go to refresh the page, i'm getting the following error:
TypeError: Cannot read property 'favorites' of null
Here is relevant code for my Favorites component. The error is triggered on the loggedUser.favorites data. I can see when visiting the favorites page, the loggedUser field is there and data displays fine but on refresh the loggedUser variable turns to null.
const searchCards = ({ loggedUser, search }) => {
const favorites = loggedUser.favorites;
console.log("FAVORITES", favorites);
return search
? favorites.filter(a =>
a.title
.toString()
.toLowerCase()
.includes(search.toLowerCase())
)
: favorites;
};
const Cards = props => {
useEffect(() => {
setData(props.cardsToShow);
}, [props]);
const [filteredData, setData] = useState(props.cardsToShow);
const mapStateToProps = state => {
return {
baseball: state.baseball,
loggedUser: state.loggedUser,
page: state.page,
entries: state.entries,
query: state.query,
pageOutput: state.pageOutput,
search: state.search,
cardsToShow: searchCards(state)
};
};
const mapDispatchToProps = {
searchChange,
fetchData,
updateUser
};
I tried to add this before i render the data, but it's not working
if (!props.loggedUser) return null;
How can i retain that state if a user is refreshing the page. The odd part is that on my home page where i have a similar sort of display a refresh isn't causing the same problems.
check once loggedUser is exist in state or not. Print state using console.log(state). you may also open inspect tool and go to application tab and click on local storage, you will get localStorage data.
Well, i figured this out and got some help from this post here. Redux store changes when reload page
My loggedUser state was disappearing after reload, so i just loaded the inital state for loggedUser pulling the data from the local storage:
function initState() {
return {
token: localStorage.token,
firstName: localStorage.firstName,
id: localStorage.id,
favorites: localStorage.favorites,
username: localStorage.username
};
}
const loginReducer = (state = initState(), action) => {
switch (action.type) {
case "SET_USER":
if (action.data) userService.setToken(action.data.token);
return action.data;
default:
return state;
}
};

Categories