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: [/* ... */]
}
}
Related
I have react app, and I am using redux as a store. Along with redux i am using redux-thunk. For example, i have action getUsers that fetch all users and storing them in user reducer. Also, if there is some error while fetching them i store that error. My question is how to in react component named UsersOverwiew watch for changes happend in reducer, for example error, and show that error to user? I did it with useEffect hook, but is there better way?
User reducer
case GET_USERS_BEGIN:
return {
...state,
users: {
loading: true,
error: {},
data: []
}
};
case GET_USERS_SUCCESS:
return {
...state,
users: {
loading: false,
error: {},
data: action.users
}
};
case GET_USERS_FAILURE:
return {
...state,
users: {
...state.users,
loading: false,
error: action.error,
}
};
UserOverview component
// fetching users
useEffect(() =>{
getUsers();
}, []);
// watch for changes in user reducer
useEffect(() =>{
if(users.error){
// if error happend do something
}
}, [users]);
This is only small part of code, i have already everything connected, component and reducer, but i wanted to simplify it as much as i can.
You are doing it the correct way if you are using functional components. The above answer which suggests componentWillMount() requires class based components.
However doing it with useEffect is completely fine.
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!
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)?
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.
So I have an action that is fetching from an API.
I am fetching the user from my API, and it returns the JSON with and returns with the action and the json values for the user and a progress value.
fetchUser: (userId) => {
return dispatch => {
dispatch({ type: Constants.USER_FETCHING });
let url = 'http://localhost:4001/api/v1/users/'+ userId;
axios.get(url)
.then(function(data) {
console.log('data returned is ' + JSON.stringify(data));
dispatch({
type: Constants.GET_USER,
user: data.user,
progress: data.progress,
});
});
};
},
Now in my reducer I have to return the state without mutating it:
import Constants from '../constants';
const initialState = {
users: [],
user: null,
progress: null,
fetching: false,
};
const users = (state = initialState, action) => {
case Constants.USER_FETCHING:
return {...state, fetching: true};
switch (action.type) {
case Constants.GET_USER:
return ?????;
default:
return state;
}
};
export default users;
How exactly should I be returning the state?
I see examples using Object.assign but not sure how to structure it for my case:
return Object.assign({}, state, ????);
You can use the spread operator ... as you did in your USER_FETCHING:
case Constants.GET_USER:
return {
...state,
user: action.user,
progress: action.progress
};
This creates a new object by first setting the same properties as they currently are on state, and then overwrites the user and progress properties with the new values from the action.
Both Karbaman and Tony answers are correct. Furthermore, the Object spread is compiled to Object.assign by default if you're using babel, as you can read in the documentation.
To add to those answers, if you want to update the users array, you can use the spread transform (documentation):
case Constants.GET_USER:
return {
...state,
user: action.user,
progress: action.progress,
users: [ ...state.users, action.user ]
};
Which will create a new array for users, concat it with the existing one, then with the new user.
However, if the user in the action is already within the existing array, it will get duplicated. You can implement a verification to avoid that, or use directly union from lodash (if user is a String or Number):
....
users: union(state.users, [action.user])
...
If you are going to use Object.assign, then it will be:
return Object.assign({}, state, {user: action.user, progress: action.progress});
How it works:
Object.assign gets 3 objects:
{} - empty
state
{user: action.user, progress: action.progress}
and merges them into 1 object one by one.
It is important to have empty object ( {} ) as a first argument, because in this case props/values from state will be merged to empty object and you will have a new copy.
If you remove empty object or put state as a first argument - in this case everything will be merged to state and you will have a mutation instead of copying.