I am working on building a redux app and I need data from my database to end up in my redux store after a particular component is loaded. However, I need this to happen every time a user adds new data. Currently, I can either get the data to load initially by passing an empty dependency array to my effect, but this stops the data from being updated based on user actions (you would have to refresh the page to get the effect to run again). But if I add the data to my dependency array, this causes an infinite loop because the effect would be changing the data that is being watched in the dependency array. Below is the current flow of my code.
useEffect dispatches action from MarkerList component.
Redux action retrieves data from Firestore and dispatches action to the reducer.
Reducer updates state with payload from Firestore.
I don't know how to correctly get my effect to trigger every time a new marker is added. If I can't track my data in the dependency array due to an infinite loop, how am I supposed to monitor changes to the data and retrigger the effect?
I have tried setting up a firestore onSnapshot listener but that doesn't play nicely with redux. I have tried using an empty dependency array as stated previously which correctly fetches data the first time, but does not update my ui when new data is added without refreshing the page. Obviously, tracking the markers in the dependency array causes an infinite loop.
Effect in component
useEffect(() => {
const loadMarkers = async () => {
props.loadMarkers();
};
loadMarkers();
}, []);
Redux Action
export function loadMarkers() {
let markers = [];
return dispatch => {
getMarkersFromDB().then(snap => {
snap.forEach(doc => {
markers.push(doc.data());
});
dispatch({
type: constants.LOAD_MARKERS,
payload: markers
});
});
};
}
I think I figured it out. I was so wrapped around trying to figure out how to make the effect handle all data manipulations, I didn't think to just call loadMarkers again from the function that adds a marker to my database. The CreateMarker component is separate so I just wired it up to redux and passed the loadMarkers action to it. Now, my MarkerList still loads the initial data on mount, but whenever a user creates a new marker, the CreateMarker components also dispatches the loadMarkers action. Everything is working now.
Thanks Azundo, talking through it with you made me realize I was being shortsighted.
Related
Background
I am having react application which uses redux and redux-thunk.
Trying to understand how to orchestrate async calls. How to dispatch actions only if previously dispatched action (API call) was successul.
Use Case
I have EmployeesComponent. That component has two dummy presentational components
EmployeesList which basically lists employees.
AddEmployeeForm which is a modal for adding new employees.
When rendering the EmployeesComponent, within the useEffect hook, I dispatch an action to load employees. So far, so good.
When I user enters data in the AddEmployeeForm and submits the form, I want to achieve the load all employees again if the API call was successful.
I have issues understanding how to know if I am okay to dispatch the action to load all employees. Here is how my handler for submit looks like.
const handleSubmit = async (e) => {
e.preventDefault();
console.log("Handle submit add new employee..");
const employeeData = inputs;
// Validate data from the AddEmployeeForm
// in case of validation errors, abort
dispatch(addEmployee(employeeData)); // calls API to save the data
// How to await the result of the dispatched action (addEmployee) before continuing?
// If addEmployee was not successful abort
// The error data from the API call is saved in the redux store,
// so the UI gets notified and can display it properly
// If addEmployee was successful, I want to
clearForm(); // local state from the component
handleCloseForm(); // local state from the component
dispatch(loadEmployees());
};
Challenges
The current approach in handleSubmit smells to me. The business logic is kinda placed in the UI and the benefits of clearly separating the UI and business logic are lost. I have difficulties to figure out what is the approach to go and how to handle this use cases.
Another use case example:
dispatch login user
if login was successful, you get token from the backend
if login was successful, dispatch an action to load user data based on retrieved token
In Redux, async logic is normally written in the form of "thunk" functions. That lets you extract logic that needs to interact with the store outside of your React components, especially async logic that needs to do things like fetching data and dispatching the results.
You can also return a promise from a thunk and await that promise in the component after dispatching.
I am currently building a like button on my card component in vue. I am fetching data from firebase using middleware on a page to dispatch the vuex action that will go and get my user info which has their liked_posts stored in an array.
The issue comes up that when I load a page requiring some of the data
i.e. liked_posts and my state is empty it throws a error of
"undefined".
How can I make sure that even if the user hasn't signed in or hasn't ever visited that my user data wont cause an error
I have tried to change my action in the Vuex store to be asynchronous and use await so that I made sure the data was there, but it didn't help.
What is happening is the below code in computed properties is trying to access an object that doesn't exist in the array yet.
likedOrNot() {
const likeInfo = this.$store.state.userInfoSub[0].liked_posts
return likeInfo.includes(this.$store.state.loadedCards[0].id)
}
This data isn't there yet because the user isn't signed in, exist ect. once they do and middleware is dispatching an action to fetch the user data the userInfoSub will be filled with info.
my base state looks like this when the user hasn't signed in or middleware hasnt fired to look for the user that gets put in cookies.
So I need away to ensure my lack of userInfoSub doesn't break my computer property
loadedCards:Array[1]
0:Object
token:null
user:null
userInfoSub:Array[0]
username:null
Here's an opinionated answer: use get from lodash.
npm i lodash
Then you can write something like this:
import get from 'lodash/get';
export default {
computed: {
isLiked() {
const cardId = get(this.$store, 'state.loadedCards[0].id');
const postIds = get(this.$store, 'state.userInfoSub[0].liked_posts', []);
return postIds.includes(cardId);
},
},
};
Maybe I'm thinking about this wrong, but a common pattern I use with redux-thunk is to return a promise so I can perform some additional actions in the container object when something completes or fails.
Using Thunk as an example:
Action Creator:
const action = ({ data }) => (dispatch) =>
fetch(`some http url`)
.then(response => {
if(response.ok) {
return response.json();
}
return Promise.reject(response);
})
Somewhere in the connected component:
this.props.action("Some Data")
.then(console.log)
.catch(error => console.error("ERROR"));
Is there a clean way of doing something similar in Redux-Observable/Rxjs? Basically returning a promise from an action, calling an epic, that returns resolve or reject once the observable is complete.
Generally speaking, I try to shy people away from these patterns when using things like redux-observable/redux-saga. Instead, if you're waiting for an action you dispatched to update the store's state, rely on that state update itself or some sort of transactional metadata your reducers store in the state about it. e.g. { transactions: { "123": { isPending: true, error: null } }
However, if you really do want to do it, you can write (or use an existing) middleware that returns a Promise from dispatch that will resolve when some other specified action has been dispatched (presumably by your epics). redux-wait-for-action is one example (not a recommendation, though cause I haven't used any)
Keep in mind that Promises are not normally cancellable, so you could accidentally create a situation where a component starts waiting for an action, the user leaves that page and the component is unmounted, and you're still waiting for that other action--which can cause weirdness later, like if they come back to that page, etc.
If you're waiting to chain side effects, that belongs in your epics. Kick off a single action that an epic listens for, makes the side effect, then it emits another action to signal completion. Another epic listens for that completion action and then begins the other side effect, etc. You could put them all in a single monolithic epic, if you want, but I find the separation easier for testing and reuse.
I am creating simple app, which makes GET requests to the server, then prepares recieved data and creates chart. There are few questions:
Where should I place code responsible for checking and preparing raw data. Currently I have it in my action creators, but maybe it needs to be in the component itself?
I need to check and compare prepared data with the data which is already used for the chart, and do not call re-render if it's the same or not valid. Where should I put this check? For now I think to place it inside action creators too. But for that I need to use getState() for accessing the state, doesn't look right.
Action creators seems right place for all these checks for me, because if data is not valid, I can simply not update my state with it, (e.g. do not dispatch certain action creator) Or maybe I have to update state with new data despite it is not valid?
given these action creators, what is the best place for described checks?:
export function fetchPopulations(term = "") {
return function (dispatch) {
dispatch(fetchingPopulations())
term=toTitleCase(term)
return fetch(`${API_URL}${term.replace(/\s/g, '%20')}`)
.then(response => response.json())
.then(json => dispatch(requestPopulations(json)))
}
}
export function requestPopulations(data = []) {
return {
type: REQUEST_POPULATIONS,
payload: data,
}
}
export function fetchingPopulations() {
return {
type: FETCHING_POPULATIONS
}
}
I would say you are doing it right.
In your example, requestPopulations and fetchingPopulations are the real action creators and fetchPopulations is a composing function (yes, composing functions for the win!).
Where should I place code responsible for checking and preparing raw
data. Currently I have it in my action creators, but maybe it needs
to be in the component itself?
Components are not the place for putting the business logic of our application. Components should only represent the View in our MVC. No API calls, no business logic, only props and state.
I need to check and compare prepared data with the data which is
already used for the chart, and do not call re-render if it's the same
or not valid. Where should I put this check? For now I think to place
it inside action creators too. But for that I need to use getState()
for accessing the state, doesn't look right.
Create modular functions (it really shines with code maintenance and reuse) for performing these checks, compose them together in another one along with your real action creators, and you can dispatch only if needed. Further optimization can be done inside component life cycle hook shouldComponentUpdate(nextProps, nextState). Also I think it is definitely not an anti-pattern to use methods with a signature like this:
export function myComposingFunction(params) {
return (dispatch, getState) => {
// ...
So you can use getState().
Action creators seems right place for all these checks for me, because
if data is not valid, I can simply not update my state with it, (e.g.
do not dispatch certain action creator) Or maybe I have to update
state with new data despite it is not valid?
No, do not update the state with useless data. If you do that you will re-render the entire tree for nothing. You were absolutely right to say "if data is not valid, I can simply not update my state with it, (e.g. do not dispatch certain action creator)"
Project (Todolist) was created with immutable library, source here
Store structure: project have many tasks, In redux store: State - map, projects, tasks - Records
When I asyncly remove project ...
export const removeProject = project => (dispatch) => {
if (!isProjectExist(project)) return Promise.resolve()
return projectService
.delete(project)
.then(
() => {
dispatch(remove(project))
console.log("post removeProject resolved")
},
handleError,
)
}
.... that was created after initialization - it will be deleted and properly unmounted, but when project was passed as initialState - ProjectList will not be rerendered, and ProjectItem try to render itself with stale data, and fail, as in picture
It have tests
It looks like reducer returs changed data, but I use immutablejs, and previously i use normalizr-immutable, but I thought that source of issue in this library and write my own normalizeInitialState (source), it did not help, now I think that maybe source of problem in redux-immutable
I struggled entire day on solving of this problem
creator of redux says
I don't think this is something we can fix. React state changes are
asynchronous and React may (or may not) batch them. Therefore, the
moment you press “Remove”, the Redux store updates, and both Item and
App receive the new state. Even if the App state change results in
unmounting of Items, that will happen later than mapStateToProps is
called for Item.
Unless I'm mistaken, there is nothing we can do. You have two options:
Request all required state at App (or a lower, e.g. ItemList) level
and pass it down to “dumb” Items. Add safeguards to mapStateToProps
for “currently unmounting” state. For example, you may return null
from render in this case. Potentially we could have the component
generated by connect() return null from its render if mapStateToProps
returned null. Does this make any sense? Is this too surprising?
Hm, I never saw stubs like return (<div></div>) or safeguards in mapStateToProps in others code
markerikson
I'm not entirely sure I follow what exactly your problem is, but as a
guess: it sounds like the child component is re-rendering before the
parent is. This is a known issue with React-Redux v4 and earlier. The
v5 beta fixes that issue. Try installing react-redux#next and see if
that takes care of your problem.