Confuse about react redux reducer state design - reactjs

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.

Related

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: [/* ... */]
}
}

How to share/split a Redux Store between multiple generic components?

I have a generic component called "VendorResults". I am passing a string prop down to each of these generic components such as "Microsoft", "Apple", etc.
<ScrollView>
<SearchResults/>
<VendorResults vendor={"microsoft"}/>
<VendorResults vendor={"oracle"}/>
</ScrollView>
Within this generic component, I am passing the vendor prop as a parameter to my Redux-Thunk actions as such:
componentDidMount() {
const {vendor} = this.props;
this.props.getVendorInformation(vendor);
}
An API call kicks off, and Thunk actions are dispatched. The data eventually makes its way to the Reducer and store. However, When I have more than one generic Vendor component, whichever async call finishes last, appears to take precedent over all the others. For example, if oracle finishes loading last, the microsoft component's state will change and show oracle data.
Actions
export function getVendorInformation(vendor) {
const url = `${VENDOR_URL}api/search/${vendor}`;
return dispatch => {
dispatch(getVendor());
fetch(url)
.then(blob => blob.json())
.then(data => {
dispatch(getVendorSuccess(data))
})
.catch(e => {
console.log(e);
dispatch(getVendorError(e.message))
});
};
Reducer
export default function(state=initialState, action){
switch (action.type){
case FETCHING_VENDOR: return {payload:[], fetching: true}
case FETCH_VENDOR_SUCCESS: return {payload: action.payload.data}
case VENDOR_ERROR: return {payload:[], error: true, ...state}
}
return state;
}
My Question:
I want to maintain this pattern of generic/reusable Vendor components - I do not want a new component for each vendor. The same goes for actions/reducers; unique vendor actions/reducers would get messy.
How can I share/split/partition a single Redux store into vendor specific chunks to maintain seperation of state but still benefit from one flow. Thank you!!
You need to pass vendor to reducer via action and re-do structure of your state. If list of vendors is pre-determined and not very long, it probably will be less messy to just create separate actions/reducers.
Otherwise, you need to have nested reducer:
const supportedActions = [FETCHING_VENDOR, FETCH_VENDOR_SUCCESS, VENDOR_ERROR];
const initialVendorState = {data:[], fetching: false, error: false};
const vendorReducer = (state = initialVendorState, action) => {
switch (action.type){
case FETCHING_VENDOR: return {data:[], fetching: true}
case FETCH_VENDOR_SUCCESS: return {data: action.payload.data}
case VENDOR_ERROR: return {...state, data:[], error: true}
}
return state;
}
const reducer = (state = {}, action) => {
if (supportedActions.includes(action.type)) {
const s = {};
s[action.payload.vendor] = vendorReducer(state[action.payload.vendor], action);
return {
...state,
...s
};
}
return state
}
export default reducer;
And your action creators should take vendor as parameter and pass it to reducer:
const fetchVendorSuccess = (vendor, data) => ({
type: FETCH_VENDOR_SUCCESS,
payload: {
vendor,
data
}
});
In your connect function you will need to use smth like data: (state[vendor] || {}).data to avoid errors if state does not have any info about that vendor
However, When I have more than one generic Vendor component, whichever async call finishes last, appears to take precedent over all the others. For example, if oracle finishes loading last, the microsoft component's state will change and show oracle data.
You are seeing Oracle data because after fetching the vendor data you are overwriting the entire vendor state with the latest array of vendor items.
case FETCH_VENDOR_SUCCESS: return {payload: action.payload.data}
To avoid this, you would need to merge the previous state with the new state.
Solution depends on what each vendor response looks like. As Gennady suggested, you can use an object and make each vendor a property on the object.
Using a flat array to store all the different vendor items presents challenges. How would you determine if a vendor has already been fetched?
To avoid overwriting the previous vendor, you would need to merge the new state with previous state. E.g.
case FETCH_VENDOR_SUCCESS: return [...state.data, ...payload.data]

same action type for combined reducers

I'm new to React, just a question on Reducers. Below is some example code from a book:
export default createStore(combineReducers(
{
modelData: ReducerOne,
stateData: ReducerTwo
}));
And the book says:
Each reducer operates on a separate part of the data store, but when an action is processed, each reducer is passed the action until one of them returns a new data store object, indicating that the action has been processed.
Below is my questions:
Q1. Let's say there is action and the action type is called "Update", only ReducerTwo handle this action type. Does it mean that ReducerOne will still be called first then ReducerTwo gets called after? if yes,this is inefficient, isn't it? Image there is a lot of reducers, most of them who don't handle the relevant type will be called until the correct reducer is called?
Q2. Does it mean that the action types have to be unique in all reducers? For example, ReducerTwo handle "Update" actiont type, so ReducerOne cannot handle "Update" , and we have to modify the name to something like "Update_One"? And each time we add a new reducer, we need to check all existing reducers to see if there will be duplicated name of action types, which is a tedious and error-prone process?
This is an example of a reducer:
export default (state=INIT_STATE, action) => {
switch(action.type) {
case CRUD_CREATE_SUCCESS: {
return {
...state,
alertMessage: 'Registro creado correctamente.',
showMessage: true,
currentPage: 0,
selectedItem: null
}
}
case CRUD_DELETE_SUCCESS: {
return {
...state,
alertMessage: 'Registro eliminado correctamente.',
showMessage: true,
currentPage: 0,
selectedItem: null
}
}
case CRUD_UPDATE_SUCCESS: {
return {
...state,
alertMessage: 'Registro actualizado correctamente.',
showMessage: true,
}
}
}
};
And all of them have exactly the same format.
As you can see, there is no way to know which one addresses each action. So it basically follows a chain of responsibility pattern.

Redux, React for single page application login and logout

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!

can i chain redux promsie action in react component?

I have a redux action which get data from server my action is like this
export const getFakeData = () => (dispatch) => {
return dispatch({
type: 'GET_FAKE_DATA',
payload: {
promise: axios.get('/test'),
}
});
};
my reducer is like this
const reducer = (INITIAL_STATE, {
[GET_FAKE_DATA]: {
PENDING: () => ({
isLoading: true,
}),
FULFILLED: (state, action) => {
const { data } = action.payload.data;
return {
...state,
data,
error: false,
isLoading: false,
};
},
REJECTED: () => ({
isLoading: false,
error: true
}),
});
I want to show success alert after my action sent, is below code breaks the principle of redux about one way flow?
this.props.getFakeData().then(() => {
this.setState({
showAlert: true
});
});
According to your use-case, it's perfectly fine to keep showAlert flag in the component's local state, instead of the Redux store.
Here's what Redux official documentation stands for:
Using local component state is fine. As a developer, it is your job to
determine what kinds of state make up your application, and where each
piece of state should live. Find a balance that works for you, and go
with it.
Some common rules of thumb for determining what kind of data should be
put into Redux:
Do other parts of the application care about this data?
Do you need to be able to create further derived data based on this original data?
Is the same data being used to drive multiple components?
Is there value to you in being able to restore this state to a given point in time (ie, time travel debugging)?
Do you want to cache the data (ie, use what's in state if it's already there instead of re-requesting it)?

Resources