Redux, React for single page application login and logout - reactjs

I'm trying to use redux for a single page application. The reasoning behind that is if there's a logout due to token validation timeout or an error I can call the store from the children components and set actions respectively to be called, but I'm not sure how to do this and I'm not 100% sure this is probably usage of Redux
function reducer(state, action) {
if(action.type === 'timeOut'){
this.setState({
loggedIn : false,
logoutMessage : 'Your session has timed out',
errorOpen : true,
});
}else if(action.type === 'error'){
this.setState({
loggedIn : false,
logoutMessage : 'An error has occured',
errorOpen : true,
});
}else if(action.type === 'logout'){
this.setState({ loggedIn : false });
}
}
const store = createStore(reducer);
export default class LoginRegister extends Component {
constructor(props){
super(props);
this.state = {
onLoginPage: true,
loggedIn: false,
loginError: false,
logoutMessage: '',
errorOpen: false,
}
}
Below is my sample code. What I was going to do is create the actions then pass the store down to children components that make REST calls and if the response for any of the REST calls was 401 with a timed out message it'd dispatch an action telling this main page to set loggedin to false. Any suggestions and Redux practice advice would be great!

I think you should look at some more documentation, a reducer is like... a chunk of your store, containing data, and changing that data based on your actions. let's say you only have one reducer, your state data will live inside of solely that one (state.reducer). Otherwise it will be spread out over all the reducers you create(when using combineReducers). This slightly modified example comes from the redux reducers documentation:
const initialState = {
todo: null,
showTodo: false,
}
function todoAppReducer(state = initialState, action) { // state is equal to initialState if null
switch (action.type) { // switch is like if/else if, up to you which to use
case SET_MY_TODO:
return Object.assign({}, state, { // Here they use object assign,
todo: action.todo, // but you can use the new spread operators if you want.
}) // the next case is an example of this.
case SHOW_MY_TODO:
return {
...state, // this will make a new object, containing all of what
showTodo: true, // state contained with the exeption of setting showTodo
} // to true
case HIDE_MY_TODO:
return {
todo: state.todo,
showTodo: false, // this is what both of those methods do in an explicit way
}
case CLEAR_MY_TODO:
return Object.assign({}, state, {
todo: null,
})
default:
return state
}
}
their examples use switch/case, this is more of a preference from what I know, but when it comes to how to change state, they don't actually call setState(), they only need to return the NEW state object for that particular reducer(in your case it's called reducer) based on what the action.type and action.xxx(potential parameters) are. In your code, you need only return the new state!
// Here I recommend you use constants for your action types,
// ex: export const TIMEOUT = 'TIMEOUT'; in an actionsTypes.js file for example
// That way if you modify ^^^ it will propagate to all files everywhere,
function reducer(state, action) {
if(action.type === 'timeOut'){ // using what I showed in the
loggedIn : false, // example, you need
logoutMessage : 'Your session has timed out',// to return objects for each
errorOpen : true, // if/else if that define the
}); // NEW state for this reducer!
}else if(action.type === 'error'){
this.setState({
loggedIn : false,
logoutMessage : 'An error has occured',
errorOpen : true,
});
}else if(action.type === 'logout'){
this.setState({ loggedIn : false });
}
}
Then, using react-redux, you connect your React component to your store(the thing that contains your state). And that gives you access to the whole state tree(using mapStateToProps) and access to your actions(so that you can call them from react) with mapDispatchToProps!
This is my first answer, hope it's not too messy! Sorry!

Related

When using useReducer to control a form, do I pass the previous state on every action?

My form has two inputs:
username (type="text")
email (type="email")
I also have a .json file as a "database" to do some validation. The submit button can only be clicked if the user enters a username that doesn't exist on the .json file. The thing is I don't know if I should pass the "...state" object on every action.type. I am getting some bugs that seem to go away when I use the "...state", but I don't understand why nor if I should always use it.
Here's my code:
const formReducer = (state, action) => {
switch (action.type) {
case "USERNAME_INPUT":
return {
...state,
usernameValue: action.payload,
};
case "API_RETURN_USERNAME":
return {
...state,
usernameValue: action.payload.match
? action.payload.username
: "",
usernameIsValid: !action.payload.match,
emailValue: action.payload.match ? action.payload.email : "",
emailIsValid: !action.payload.email,
apiReturned: true,
};
case "EMAIL_INPUT":
return {
...state,
emailValue: action.payload.value,
emailIsValid: action.payload.isValid,
formIsValid: action.payload.isValid && state.usernameIsValid,
apiReturned: false,
};
default:
return {
usernameValue: "",
emailValue: "",
usernameIsValid: false,
emailIsValid: false,
formIsValid: false,
apiReturned: false,
};
}
The reducer is always called with 2 arguments:
The previous state value and
The action (most often, an object)
The role of reducer function is to compute on these 2 values and generate the next value of state(read a new state object) and discard old state
All action may not change all keys of your state which is typically, and even in this particular case, a object.
We may only want to operate on a few keys of our state object.
So, we have two options, either manually copy all keys or use ES6 spread operator to spread the old state object and then overwrite the keys we want to change with the new value. This will preserve the keys we are not changing.
If you don't take either path, your state will become the object with only the keys you update and hence you may face unexpected behaviour
You should just about always shallow copy the previous state when updating a React state. Think of the useReducer as a very specialized version of the useState hook. Instead of returning the state and an updater function, i.e. [state, setState], it returns an array of the state and a dispatch function, i.e. [state, dispatch]. Please recall that state updates in React function components do not merge updates, so failing to copy any of the previous state will result in it not being included in the next state value, as if it were removed.
Functional Updates
Note
Unlike the setState method found in class components, useState
does not automatically merge update objects. You can replicate this
behavior by combining the function updater form with object spread
syntax:
const [state, setState] = useState({});
setState(prevState => {
// Object.assign would also work
return {...prevState, ...updatedValues};
});
Another option is useReducer, which is more suited for managing
state objects that contain multiple sub-values.
From this basic understanding of state updates in function components, or if you are already familiar with "legacy" Redux reducer functions, it is trivial to see and understand why the previous state is copied forward.
Note also the default case typically doesn't have any effect on the state, it just returns the current state value.
The single exception I can think of is when you wanting to set the state to a completely new value like resetting back to initial state.
Example:
const formReducer = (state, action) => {
switch (action.type) {
case "USERNAME_INPUT":
return {
...state,
usernameValue: action.payload,
};
case "API_RETURN_USERNAME":
return {
...state,
usernameValue: action.payload.match
? action.payload.username
: "",
usernameIsValid: !action.payload.match,
emailValue: action.payload.match ? action.payload.email : "",
emailIsValid: !action.payload.email,
apiReturned: true,
};
case "EMAIL_INPUT":
return {
...state,
emailValue: action.payload.value,
emailIsValid: action.payload.isValid,
formIsValid: action.payload.isValid && state.usernameIsValid,
apiReturned: false,
};
case "RESET":
return action.payload;
default:
return state;
}
};
...
const initialState = {
usernameValue: "",
emailValue: "",
usernameIsValid: false,
emailIsValid: false,
formIsValid: false,
apiReturned: false,
}
...
const [state, dispatch] = useReducer(reducer, initialState);
...
const resetState = () => {
dispatch("RESET", { payload: initialState });
};

best way to handle fetching Status in redux

I'm looking for the best way to handle my fetching status in my app,
the simplest way is to create an isFetching[actionName] for each action and then the state will look something like this:
state:{
todos:[...],
user[...],
isTodosFetching:true/false,
isUserFetching:true/false
}
but I'm looking for a more elegant way of storing fetching statuses in the store.
so I tried to think of an alternative way and thought about create a fetchingActionsReducer that will add each fetching action to a dict(object) in the store and then the state will look like this:
todos:[...],
user[...],
loadingTasks:{
isTodosFetching:true/false,
isUserFetching:true/false
}}```
now every component will get loadingTasks with mapStateToProps and that's it.
this will reduce the boilerplate to one simple reducer and one action.
reducer:
export const loadingTasks = (state = {}, action) => {
switch (action.type) {
case START_LOADING_TASK:
return Object.assign({}, state, { [action.payload]: true });
case END_LOADING_TASK:
return Object.assign({}, state, { [action.payload]: false });
default:
return state;
}
};
actions:
export const startLoadingTask = (taskName) => ({
type: START_LOADING_TASK,
payload: taskName,
});
export const endLoadingTask = (taskName) => ({
type: END_LOADING_TASK,
payload: taskName,
});```
I tried it it works perfect but I would like to know,
1. there is any better way to handle fetching status with redux?
2. now many portfolios will be subscribed to the loadingTasks state and I'm afraid it will cause performance issues. (for every change in the loading tasks all react will run the digging algorithm for all the components subscribed)
I suggest co-locating the fetching status with the resource being requested, for example:
state:{
todos: {
isFetching: true, // or false
data: [/* ... */]
},
user: {
isFetching: true, // or false
data: [/* ... */]
}
}
This way when the fetching status of todos change only the components dependant on todos will rerender.
This approach also enables additional statuses.
For example if the todos request fails you could have an error status. Or if the user request fails an error message providing some context, or even containing the error returned from the API:
state:{
todos: {
isFetching: false,
hasError: true, // or false
data: [/* ... */]
},
user: {
isFetching: false,
errorMessage: 'Please check username exists', // or null
data: [/* ... */]
}
}

Confuse about react redux reducer state design

For example, I have a reducer to manipulate user state, like creating user, editing, fetching user.
should I create state for each action, like:
{
loadingForFetching: false,
fetchingResult: null,
fetchingError: '',
loadingForEditing: false,
editingResult: null,
editingError:'',
loadingForCreating: false,
creatingResult: null,
creatingError: ''
}
Or let the three action share the same state:
{
loading: false,
result: null,
error: ''
}
I think the share one is a bad idea, because when fetch and create a user at the same time, if one action is complete, by setting loading to false, it may mislead another action is complete too. However, the use case is rare, maybe I worry too much
Am I right?
I don't think data like "loading" or "error" belongs in a global data store and should rather be kept as local component state. It's not persistent, and shouldn't be needed by other components. The only thing out of these I might put in a global data store would be a user entity itself, because that might be accessed by other components.
It is data-structure-wised.
So far I use
{
targetState: {
data: [{id: 1...}, {id: 2} ... ],
loading: false,
loaded: false,
error: null,
... // you could put more information if you like, such activeTarget etc
}
}
if there is more nested data there, need to consider normalizing state shape.
https://redux.js.org/recipes/structuring-reducers/normalizing-state-shape
Hope this helps.
If you want to keep loading status in your state, then you better use counter pattern, but not true/false flag.
const loadingReducer = (state = 0, action) => {
if (action.type === 'LOADING_FOR_EDITING' ||
action.type === 'LOADING_FOR_CREATING' ||
...) {
return state + 1;
}
if (action.type === 'LOADING_FOR_EDITING_SUCCESS' ||
action.type === 'LOADING_FOR_CREATING_SUCCESS' ||
...) {
return state - 1;
}
return state;
};
// root reducer:
export default combineReducers({
loading: loadingReducer,
loadedData: loadedDataReducer,
// ...etc
});
Now in your components you are able to check loading status, for example like this const isLoaded = state.loading === 0;
This approach allows you to control multiple async requests.

How to Better Handle Error and Updates in Simple React-Redux Reducer

I've been reading about normalizing state that gets returned from my reducer (React-redux) and I think I understand that if I don't spread (...) the state returned, that my React app will not recognize that a state change has happened and my UI will not update.
That is, if I return some thing like:
data: action.payload.data // where this is an array
instead of what I have below
...action.payload.data
My UI does not update (no render() called).
Assuming this is correct (which my testing seems to show), then I'm perplexed on how I can return an error condition. What I've done below is returned isLoading and isErrored as properties in place of the single array property.
This feels ugly. Like I'm using my return to mean one thing on success and another thing on errors.
Suggestions for a better way to do it? What am I missing?
export function sessionAttendee(state = {}, action) {
switch (action.type) {
case SESSIONATTENDEE_LOAD: {
return Object.assign({}, state, {
isLoading: true,
hasErrored: false
});
}
case SESSIONATTENDEE_LOAD_SUCCESS: {
return Object.assign({}, state, {
...action.payload.data
});
}
case SESSIONATTENDEE_LOAD_FAIL: {
console.log("SESSIONATTENDEE_LOAD_FAIL");
return Object.assign({}, state, {
isLoading: false,
hasErrored: true,
errorMessage: action.error.message
});
}
Object.assign returns a new object. This will ensure that you don't mutate the previous state. There's plenty of good reasons in this GitHub issue as to why you want to avoid mutating your state.
In regards to your statement "This feels ugly. Like I'm using my return to mean one thing on success and another thing on errors" I would say that you aren't actually using your returns to do multiple things. They always return the state object for that reducer. All that is different in the switch statements is that you're updating properties of that object.
FWIW, I've noticed that you are spreading within an Object.assign which is probably unnecessary. Here's how I would re-write your reducer so it would make more sense to me...
const initialState = {
isLoading: false,
hasErrored: false,
errorMessage: null
data: [],
}
export function sessionAttendee(state = initialState, action) {
switch (action.type) {
case SESSIONATTENDEE_LOAD: {
return {
...state,
isLoading: true,
hasErrored: false
}
}
case SESSIONATTENDEE_LOAD_SUCCESS: {
return {
...state,
data: action.payload.data
}
}
case SESSIONATTENDEE_LOAD_FAIL: {
return {
...state,
isLoading: false,
hasErrored: true,
errorMessage: action.error.message
}
}
default:
return state;
}
}
defined an initialState as that makes it somewhat easier to reason when reading. Not a requirement, but I find it helps.
switched from Object.asssign to using the ES6 spread operator.
Ensured that your action.payload.data key was actually assigned to a property on the state object, rather than just smishing it in.

Generate reducers dynamically (universal reducer for api calls)

In redux how can i make reducers dynamically based on api call passed as string to an action-creator to reduce the boilerplate (so for each api call there was a dedicated key inside the store)?
And should i even try to do that?
Example jsfiddle
The problem is here:
export function universalFetchReducer(state = initialState, action) {
switch (action.type) {
case 'FETCHING_DATA' + action.metadata:
return {
...state,
isFetching: true
};
case 'FETCHING_DATA_SUCCESS' + action.metadata:
return {
...state,
isFetching: false,
data: action.data,
dataFetched: true
};
case 'FETCHING_DATA_FAILURE' + action.metadata:
return {
...state,
isFetching: false,
error: true
};
default:
return state;
}
}
For now i can create actions and their names based on url passed to an action-creator, but cannot make a dedicated reducer.
Solved this by using redux-injector, followed its api to create an action creators and a simple async action creator (axios used):
export function getData(api) {
return {
type: `FETCHING_DATA_${api}`,
meta: api
}
}
export function universalFetchData(api) {
injectReducer(`universalFetch${api}`, universalFetchReducer);
return dispatch => {
dispatch(getData(api)) //Some initial action. Pass api to name actions
axios
.get(api)
.then(response => {
dispatch(getDataSuccess(response.data, api)) //Some success action
})
.catch(error => getDataFailure(error.response.status, api)) } } //Some failure action
Then just fired an universalFetchData('path_to_api') from component and got FETCHING_DATA_path_to_api action in redux-devtools.
Got data from store
state.universalFetchReducer_path_to_api
and passed this state to render with e.g. ramda's pathOr to set unkown initial state.
Lesson learned: you will be able to make many simple lazy loading api calls fast, but do this only if you know what data you're getting. For more dangerous logic use regular reducers upfront. This solution nowhere near acceptable but it gets job done.

Resources