How can I watch for changes from reducer in react component? - reactjs

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.

Related

React Redux - How to do a proper loading screen using React and Redux on url change

I am working on a web app that uses React + Redux. However, I am struggling with the loading screens. Basically, my initial state for the redux store is this.
const initialState = {
project: {},
projects: [],
customers: [],
vendors: [],
customer: {},
vendor: {},
loading: false
};
An example of one of my reducers is this
const fetchSalesProject = (state, action) => {
return updateObject(state, {
project: action.payload,
loading: false
});
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.FETCH_SALES_PROJECT: return fetchSalesProject(state, action);
default:
return state;
where updateObject is this
export const updateObject = (oldObject, updatedProperties) => {
return {
...oldObject,
...updatedProperties
}
}
here are my actions (axiosInstance is just a custom addon to axios to include the necessary headers for authentication, but works exactly the same like a normal axios api call)
export const fetchSalesProject = (id) => (dispatch) => {
axiosInstance
.get(`/sales-project/detail/${id}/`)
.then((res) => {
dispatch({
type: FETCH_SALES_PROJECT,
payload: res.data,
});
})
.catch((err) => dispatch(returnErrors(err.response.data, err.response.status)));
};
export const showLoader = (area) => (dispatch) => {
dispatch({
type: SHOW_LOADER,
area: area ? area : true
});
};
where the param area is just in the case where loading is meant to be rendered only on a specific area on the page (not relevant for this question hence when showLoader is called, no params would be passed in and hence the area would be set to true for this question)
this is my component
componentDidMount() {
this.props.showLoader()
this.props.fetchSalesProject(this.props.match.params.id)
};
this is my mapStateToProps
const mapStateToProps = state => ({
project: state.api.project,
loading: state.api.loading
})
this is my conditional rendering
render() {
return (
!this.props.loading ?
{... whatever content within my page }
: { ...loading screen }
Basically, the issue I am facing is that, when i first load this component, there would be an error, as my this.props.project would be an empty {} (due to initialState). The reason why is that before the showLoader action is dispatched fully by componentDidMount, the component renders, and hence since this.props.loading is still set to the initialState of false, the conditional rendering passes through and the this.props.project would be rendered (however since it is empty, the various keys that I try to access within the rendering would be undefined hence throwing an error).
I tried various methods to try to overcome this, but none seem like an optimum solution. For example, I have tried to set the initialState of loading to an arbitary number, like eg. 0, and only render the page when this.props.loading === false, which is only after the data is fetched. However, the issue arises when I go onto a new page, like for example, another component called CustomerDetail. When that component is mounted, this.props.loading would be false and not 0, as it was previously manipulated by the previous component that I was on (SalesProjectDetail). Hence, that same error would come up as the conditional rendering passes through before any data was actually fetched.
Another method I tried was to use component states to handle the conditional rendering. For example, I only set a component state of done : true only after all the dispatches are completed. However, I felt like it was not good practice to mix component states and a state management system like Redux, and hence was hoping to see if there are any solutions to my problem that uses Redux only.
I am looking for a solution to my problem, which is to only render the contents only after the data has been fetched, and before it is fetched, a loading screen should be rendered. All help is appreciated, and I am new to React and especially Redux, so do guide me on the correct path if I am misguided on how this problem should be approached. Thanks all in advance!
there can be more ways to do it. But to go ahead with your scheme of local state...
I think there is no problem if you use local state to keep track of load complete or underway.
You can have a local state variable as you already said.
Before dispatch set that variable to loading=true then use async/await to fetch data and afterwards set loading back to false.
I think it will work for you.

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

Redux action taking too long add one item to empty array - No Promise returned

I am using Redux to manage the state of my react app. I am creating an object, then passing this object to addTile function which is my action.
So my action.ts looks like this:
export function addTile(tile){
return {
type: "ADD_TILE",
payload: tile
}
}
My reducer.ts looks like this:
const reducer = (state = {
isPanelOpen: false,
isDiscardAllChangesOpen: false,
tiles: [],
tempTiles: [],
}, action) => {
switch (action.type) {
case "PANEL_VISIBILITY":
state = {
...state,
isPanelOpen: action.payload
};
break;
case "ADD_TILE":
state = {
...state,
tiles: [...state.tiles, action.payload]
}
break;
}
return state;
};
export default reducer;
However, if I try to use this in my component like this:
this.props.addTile(tile)
alert(this.props.tiles.length)
The length will be 0. However, the item is really added to the array, but at the time of the alert execution, the length was 0. From my reading on Redux docs, actions by default are async (or at least that's how I understand they are).
I even try to do this:
this.props.addTile(tile)
.then(response => { //some code})
Then I get cannot read property then of undefined.
Any ideas?
When you try to check the prop right after dispatching an action, React has not yet had a chance to re-render. It's not that it's "taking too long", it's that your own code is still executing. So, React has not re-rendered your component, and the prop value is still the same.
Your promise example would only work if addTile() was a thunk that returned a promise.

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)?

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