The server node.js updates data every 0.5 seconds. The client has to poll the server and fetch new data using RxJS. I have made the client to poll server, the requests are made but I cant read the response from the server. I think that the state is not updated due to poll_server returning timer.pipe() or the reducer is creating a wrong state. I came to this from my teacher's template, so why the dispatcher would return an Observable?
model.js
export function init_state(warnings) {
return {
warnings,
accept: ({ visit_site }) => { if (visit_site) return visit_site(warnings) }
}
}
dispatcher.js
import { from, of, timer } from 'rxjs'
import { concatMap, map } from 'rxjs/operators'
import { FRONT_PAGE } from './constants'
const poll_server = url => {
return timer(0, 3000)
.pipe(concatMap(() => from(fetch(url))
.pipe(map(response => response.json())))
)
}
export const server_dispatch = action => {
switch (action.type) {
case FRONT_PAGE: {
const res = poll_server('http://localhost:8080/warnings')
console.log(res)
return res
}
default:
return of(action)
}
}
reducer.js
export function reduce(state, action) {
switch (action.type) {
case FRONT_PAGE:
console.log(`REDUCER CALLED WITH ACTION ${FRONT_PAGE}`)
return init_state(action)
default:
return state
}
}
The problem is that you want to call poll_server which will start an observable stream on an ajax endpoint from within the reducer (which implies you would subscribe to this stream here, not the way you are currently using it), which isn't how redux is supposed to be used. Reducers are intended by its creator to be pure functions without causing side effects.
If you want to use redux with observables, it is advised to use custom middleware to handle these side effects. The most obvious middleware I suggest is https://redux-observable.js.org/, which I've tried in the past and works without trouble.
The documentation is exquisite, and you will use this without trouble if you are already familiar with RX principles.
Related
I have an issue where I am trying to use the Redux state to halt the execution of some polling by using the state in an if conditional. I have gone through posts of SO and blogs but none deal with my issue, unfortunately. I have checked that I am using mapStateToProps correctly, I update state immutably, and I am using Redux-Thunk for async actions. Some posts I have looked at are:
Component not receiving new props
React componentDidUpdate not receiving latest props
Redux store updates successfully, but component's mapStateToProps receiving old state
I was kindly helped with the polling methodology in this post:Incorporating async actions, promise.then() and recursive setTimeout whilst avoiding "deferred antipattern" but I wanted to use the redux-state as a single source of truth, but perhaps this is not possible in my use-case.
I have trimmed down the code for readability of the actual issue to only include relevant aspects as I have a large amount of code. I am happy to post it all but wanted to keep the question as lean as possible.
Loader.js
import { connect } from 'react-redux';
import { delay } from '../../shared/utility'
import * as actions from '../../store/actions/index';
const Loader = (props) => {
const pollDatabase = (jobId, pollFunction) => {
return delay(5000)
.then(pollFunction(jobId))
.catch(err => console.log("Failed in pollDatabase function. Error: ", err))
};
const pollUntilComplete = (jobId, pollFunction) => {
return pollDatabase(jobId, pollFunction)
.then(res => {
console.log(props.loadJobCompletionStatus) // <- always null
if (!props.loadJobCompletionStatus) { <-- This is always null which is the initial state in reducer
return pollUntilComplete(jobId, pollFunction);
}
})
.catch(err=>console.log("Failed in pollUntilComplete. Error: ", err));
};
const uploadHandler = () => {
...
const transferPromise = apiCall1() // Names changed to reduce code
.then(res=> {
return axios.post(api2url, res.data.id);
})
.then(postResponse=> {
return axios.put(api3url, file)
.then(()=>{
return instance.post(api3url, postResponse.data)
})
})
transferDataPromise.then((res) => {
return pollUntilComplete(res.data.job_id,
props.checkLoadTaskStatus)
})
.then(res => console.log("Task complete: ", res))
.catch(err => console.log("An error occurred: ", err))
}
return ( ...); //
const mapStateToProps = state => {
return {
datasets: state.datasets,
loadJobCompletionStatus: state.loadJobCompletionStatus,
loadJobErrorStatus: state.loadJobErrorStatus,
loadJobIsPolling: state.loadJobPollingFirestore
}
}
const mapDispatchToProps = dispatch => {
return {
checkLoadTaskStatus: (jobId) =>
dispatch(actions.loadTaskStatusInit(jobId))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(DataLoader);
delay.js
export const delay = (millis) => {
return new Promise((resolve) => setTimeout(resolve, millis));
}
actions.js
...
export const loadTaskStatusInit = (jobId) => {
return dispatch => {
dispatch(loadTaskStatusStart()); //
const docRef = firestore.collection('coll').doc(jobId)
return docRef.get()
.then(jobData=>{
const completionStatus = jobData.data().complete;
const errorStatus = jobData.data().error;
dispatch(loadTaskStatusSuccess(completionStatus, errorStatus))
},
error => {
dispatch(loadTaskStatusFail(error));
})
};
}
It seems that when I console log the value of props.loadJobCompletionStatus is always null, which is the initial state of in my reducer. Using Redux-dev tools I see that the state does indeed update and all actions take place as I expected.
I initially had placed the props.loadJobCompletionStatus as an argument to pollDatabase and thought I had perhaps created a closure, and so I removed the arguments in the function definition so that the function would fetch the results from the "upper" levels of scope, hoping it would fetch the latest Redux state. I am unsure as to why I am left with a stale version of the state. This causes my if statement to always execute and thus I have infinite polling of the database.
Can anybody point out what might be causing this?
Thanks
I'm pretty sure this is because you are defining a closure in a function component, and thus the closure is capturing a reference to the existing props at the time the closure was defined. See Dan Abramov's extensive post "The Complete Guide to useEffect" to better understand how closures and function components relate to each other.
As alternatives, you could move the polling logic out of the component and execute it in a thunk (where it has access to getState()), or use the useRef() hook to have a mutable value that could be accessed over time (and potentially use a useEffect() to store the latest props value in that ref after each re-render). There are probably existing hooks available that would do something similar to that useRef() approach as well.
I'm new into the React and Redux worlds and after a lot of research, I haven't found a way to handle the problem I have:
I need to perform an api call on app init, but the endpoint is in a configuration file. This configuration in the server so it has to be downloaded and read. This is because I need to distribute the app into many servers and each server has a different configuration.
Therefore the api call has to wait until the configuration has been loaded, they must be chained.
I'm using Redux to manage the state of the app so I have an action which downloads the configuration and an other action which performs the api call.
// Config action
export function fetchConfigRequest() {
return {
type: types.FETCH_CONFIG_REQUEST
}
}
export function fetchConfigSuccess(config) {
return {
type: types.FETCH_CONFIG_SUCCESS,
config
}
}
export function fetchConfig() {
return dispatch => {
dispatch(fetchConfigRequest());
return axios.get('config.json')
.then(response => {
dispatch(fetchConfigSuccess(response.data));
})
;
};
}
// Api client action
export function fetchDataRequest() {
return {
type: types.FETCH_DATA_REQUEST
}
}
export function fetchDataSuccess(data) {
return {
type: types.FETCH_DATA_SUCCESS,
data
}
}
export function fetchDataError(error) {
return {
type: types.FETCH_DATA_ERROR,
error
}
}
export function fetchData(filters = {}) {
return dispatch => {
dispatch(fetchDataRequest());
const apiClient = new apiClient({
url: state.config.apiEndpoint
});
return apiClient.Request()
.then(response => {
dispatch(fetchDataSuccess(data));
})
;
};
}
The only way that I got it working is by waiting until config action promise resolves in App component like this:
// App.component.js
componentDidMount() {
this.props.fetchConfig().then(() => {
this.props.fetchData();
});
}
But I don't think this is the best and the most "Redux style" way to do it, so how should I do it?
I've some ideas in my mind but I don't know what would be the best one:
Keep it as it is now
Create an 'app' action which dispatches the fetch config action, waits until config is loaded, and then dispatches the fetch data action
Do it into a custom middleware
Thanks!
Problem
I have an async function in redux (with redux thunk) that set a value in the redux store. It is used many times throughout my app but, I want different things to happen after the redux function runs using the new values that the redux function sets. Seems like a good reason for a callback, right? So I tried doing just this, but I got the following error:
Uncaught TypeError: Cannot read property 'then' of undefined
Logic in Attempt to fix
I thought the reason might be that although the redux function dispatches actions, it doesn't actually return anything. Therefore, I added returns in the redux function thinking that the function would return something back to the component that called it, which would resolve the error I am receiving. No Luck.
Question:
Does anyone know how I can perform functions after an async redux function (with redux thunk) runs and finishes setting a new value in the redux store?
My Most Recent Attempt to Solve
Component
import {fetchUser} from './../actions/index.js';
class MyComponent extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
// Attempt #1: this.props.fetchUserData()); // originally I attempted just running one function and then the rest without a callback but the new value was not set in redux store before the next function that needed it ran
// Attempt #2: this.props.fetchUserData().then(function() => { // I also tried running just a standard .then(function()
// Attempt #3: (line below)
this.props.fetchUserData().then((resultFromReduxFunction) => {
console.log("fetchUserData result = " + resultFromReduxFunction);
// do some stuff with the new data
console.log("this.props.reduxData.userHairColor = " + this.props.reduxData.userHairColor);
console.log("this.props.reduxData.userHeight = " + this.props.reduxData.userHeight);
});
}
}
function mapStateToProps(state) {
return {
reduxData: state.user
};
}
function matchDispatchToProps(dispatch) {
return bindActionCreators({
fetchUserData: fetchUserData
}, dispatch)
}
export default connect(mapStateToProps, matchDispatchToProps)(MyComponent);
Action Creator
export const set = (idToSet, payloadToSet) => {
return {
type: 'SET',
id: idToSet,
payload: payloadToSet
}
}
export const fetchUserData = (callbackFunction) => {
console.log("fetchUserData triggered...");
return (dispatch, getState) => {
firebase.auth().onAuthStateChanged(function (user) {
if (!user) {
console.log("no user logged in");
return false // Added in attempt to fix callback
} else {
// fetch some more user data
firebase.database().ref().child('users').child(user.uid).on('value', function(snapshot) {
var userHairColor = snapshot.child(userHairColor).val();
var userHeight = snapshot.child(userHeight).val();
dispatch(set('userHairColor', userHairColor));
dispatch(set('userHeight', userHeight));
return true // Added in attempt to fix callback
})
}
})
}
}
Redux Store
const initialState = {
userHairColor: "",
userHeight: ""
}
export default function (state=initialState, action) {
switch(action.type) {
case 'SET':
return {
...state,
[action.id]: action.payload
}
default:
return {
...state
}
}
}
Thanks in advance for any help
To use .then(...), your thunk has to return a Promise. Firebase's onAuthStateChanged seems to returns an unsubscribe function, not a Promise, so even if you returned that it wouldn't allow you to chain additional callbacks at your action creator call site. And you can't return from within the callback you pass to onAuthStateChanged, because you're in a different call stack at that point (it's asynchronous).
What you're going to have to do is pass a callback function to your action creator, which it needs to call from within the success callback of your data fetching.
Something like this:
export const fetchUserData = (callbackFunction) => {
return (dispatch, getState) => {
firebase.auth().onAuthStateChanged(function (user) {
if (!user) {
console.log("no user logged in");
// You can pass false to your callback here, so that it knows the auth was unsuccessful
callbackFunction(false);
} else {
// fetch some more user data
firebase.database().ref().child('users').child(user.uid).on('value', function(snapshot) {
var userHairColor = snapshot.child(userHairColor).val();
var userHeight = snapshot.child(userHeight).val();
dispatch(set('userHairColor', userHairColor));
dispatch(set('userHeight', userHeight));
// Passing true so your callback knows it was a success
callbackFunction(true);
})
}
})
}
}
Then to use it as desired:
this.props.fetchUserData((success) => {
console.log("fetchUserData result = " + success);
// do some stuff with the new data
console.log("this.props.reduxData.userHairColor = " + this.props.reduxData.userHairColor);
console.log("this.props.reduxData.userHeight = " + this.props.reduxData.userHeight);
});
There's a bit of callback hell going on here, but to avoid that you'd have to use a "promisified" version of Firebase's API, in which case you could also return the promise from your thunk so that anything using your action creator could attach a .then(...).
The callback would work if you actually explicitly called it inside the thunk, but it really feels like an anti-pattern.
Instead you can dispatch another thunk that can deal with any further logic.
export const fetchUserData = (callbackFunction) => {
console.log("fetchUserData triggered...");
return (dispatch, getState) => {
firebase.auth().onAuthStateChanged(function (user) {
...do stuf...
dispatch(doMoreStuff())
})
}
}
Or, if you need to react to the result inside your react component dispatch an action that will modify your redux state. That will in turn call the react life cycle methods and render and you can react to the change there based on the new state.
To improve the answer it would help to know what you want to do after the action is done.
I'm having trouble understanding how to rewrite normal action/reducer code to make use of redux-thunk or redux-promise-middleware so I can use promises.
I want to wait for my updatePhone to finish updating my state.user.information.phone before it starts testUserPhone. So obviously I need a promise to be returned from updatePhone.
this.props.updatePhone('+1**********')
.then(() => this.props.testUserPhone(this.props.user.information.phone))
action
export const updatePhone = (phone) => ({
type: UPDATE_PHONE,
payload: phone
})
and reducer
export default (state = INITIAL_STATE, action) => {
switch(action.type) {
case 'UPDATE_PHONE':
return {...state,
information: {
...state.information,
phone: action.payload
}
}
default:
return state
}
}
Should I write something like this as well as the basic function, or can I somehow combine them into one? Because I need the action to fully complete its cycle through my reducer and update phone before it comes back, but I don't want to break my reducer because now it won't be able to access payload and such since it's inside of a returned function -- super confused with how you start off using these libraries.
export function updatePhoneAsync(phone) {
return dispatch({
type: UPDATE_PHONE,
payload: phone
})
}
EDIT: So I've got this now for my action creators
export const updatePhone = (phone) => ({
type: UPDATE_PHONE,
payload: phone
})
export function updatePhoneAsync(phone) {
return function (dispatch) {
dispatch(updatePhone(phone))
}
}
Outside in my component;
this.props.updatePhoneAsync('+1**********')
.then(() => this.props.testUserPhone(this.props.user.information))
Which gives me an error 'cannot read property then of undefined'
You should write something like this if you use redux-thunk:
Action creators:
function update (params if you need them) {
return function (dispatch) {
send request here
.then(data =>
dispatch(phoneUpdated(data));
}
function phoneUpdated(phone) {
return {type: 'PHONE_UPDATED', phone};
}
Then, feel free to grab this action in your reducer and update the state as you wish.
Also, you can enhance it with additional actions in case when promise will be rejected, or at the start of request to show loader animations
I have one reducer for Clients, one other for AppToolbar and some others...
Now lets say that I created a fetch action to delete client, and if it fails I have code in the Clients reducer which should do some stuff, but also I want to display some global error in AppToolbar.
But the Clients and the AppToolbar reducers do not share the same part of the state and I cannot create a new action in the reducer.
So how am I suppose to show global error? Thanks
UPDATE 1:
I forget to mention that I use este devstack
UPDATE 2:
I marked Eric's answer as correct, but I have to say that solution which I am using in este is more like combination of Eric and Dan's answer...
You just have to find what fits you the best in your code...
If you want to have the concept of "global errors", you can create an errors reducer, which can listen for addError, removeError, etc... actions. Then, you can hook into your Redux state tree at state.errors and display them wherever appropriate.
There are a number of ways you could approach this, but the general idea is that global errors/messages would merit their own reducer to live completely separate from <Clients />/<AppToolbar />. Of course if either of these components needs access to errors you could pass errors down to them as a prop wherever needed.
Update: Code Example
Here is one example of what it might look like if you were to pass the "global errors" errors into your top level <App /> and conditionally render it (if there are errors present). Using react-redux's connect to hook up your <App /> component to some data.
// App.js
// Display "global errors" when they are present
function App({errors}) {
return (
<div>
{errors &&
<UserErrors errors={errors} />
}
<AppToolbar />
<Clients />
</div>
)
}
// Hook up App to be a container (react-redux)
export default connect(
state => ({
errors: state.errors,
})
)(App);
And as far as the action creator is concerned, it would dispatch (redux-thunk) success failure according to the response
export function fetchSomeResources() {
return dispatch => {
// Async action is starting...
dispatch({type: FETCH_RESOURCES});
someHttpClient.get('/resources')
// Async action succeeded...
.then(res => {
dispatch({type: FETCH_RESOURCES_SUCCESS, data: res.body});
})
// Async action failed...
.catch(err => {
// Dispatch specific "some resources failed" if needed...
dispatch({type: FETCH_RESOURCES_FAIL});
// Dispatch the generic "global errors" action
// This is what makes its way into state.errors
dispatch({type: ADD_ERROR, error: err});
});
};
}
While your reducer could simply manage an array of errors, adding/removing entries appropriately.
function errors(state = [], action) {
switch (action.type) {
case ADD_ERROR:
return state.concat([action.error]);
case REMOVE_ERROR:
return state.filter((error, i) => i !== action.index);
default:
return state;
}
}
Erik’s answer is correct but I would like to add that you don’t have to fire separate actions for adding errors. An alternative approach is to have a reducer that handles any action with an error field. This is a matter of personal choice and convention.
For example, from Redux real-world example that has error handling:
// Updates error message to notify about the failed fetches.
function errorMessage(state = null, action) {
const { type, error } = action
if (type === ActionTypes.RESET_ERROR_MESSAGE) {
return null
} else if (error) {
return error
}
return state
}
The approach I'm currently taking for a few specific errors (user input validation) is to have my sub-reducers throw an exception, catch it in my root reducer, and attach it to the action object. Then I have a redux-saga that inspects action objects for an error and update the state tree with error data in that case.
So:
function rootReducer(state, action) {
try {
// sub-reducer(s)
state = someOtherReducer(state,action);
} catch (e) {
action.error = e;
}
return state;
}
// and then in the saga, registered to take every action:
function *errorHandler(action) {
if (action.error) {
yield put(errorActionCreator(error));
}
}
And then adding the error to the state tree is as Erik describes.
I use it pretty sparingly, but it keeps me from having to duplicate logic which legitimately belongs in the reducer (so it can protect itself from an invalid state).
write custom Middleware to handle all the api related error. In this case your code will be more cleaner.
failure/ error actin type ACTION_ERROR
export default (state) => (next) => (action) => {
if(ACTION_ERROR.contains('_ERROR')){
// fire error action
store.dispatch(serviceError());
}
}
what I do is I centralize all error handling in the effect on a per effect basis
/**
* central error handling
*/
#Effect({dispatch: false})
httpErrors$: Observable<any> = this.actions$
.ofType(
EHitCountsActions.HitCountsError
).map(payload => payload)
.switchMap(error => {
return of(confirm(`There was an error accessing the server: ${error}`));
});
You can use axios HTTP client. It already has implemented Interceptors feature. You can intercept requests or responses before they are handled by then or catch.
https://github.com/mzabriskie/axios#interceptors
// Add a request interceptor
axios.interceptors.request.use(function (config) {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
// Add a response interceptor
axios.interceptors.response.use(function (response) {
// Do something with response data
return response;
}, function (error) {
// Do something with response error
return Promise.reject(error);
});