I just need a little help with regards to fetching of data through an api
currently I have this code in my container
fetchAll: () => {
Promise.resolve(token.getToken()).then( (response) => {
let obj = {
token_type: response.data.token_type,
access_token: response.data.access_token
}
apiFetch("http://dev.apiserver.com/api/all",{
"method": "GET",
"headers": {
"Authorization": `${obj.token_type} ${obj.access_token}`,
"userId": 1
}
})
.then( (res) => {
return res.json();
}).then( (json) => {
dispatch({
type: 'FETCH_ALL',
payload: json
})
}).catch(function(err) {
console.log(err)
})
})
}
I'm calling the above function from componentWillMount(), I can successfully log the result in my console
here is my reducer code
const allReducer = (state: Immut = initialState, action: { type: string, payload: any }) => {
switch (action.type) {
case FETCH_ALL:
let newState = state
let payload = action.payload
newState = Immutable.fromJS(payload)
console.log(newState)
return newState
}
}
the problem is that it doesn't update the state or re-render the components
Since your reducer is getting called, the issue is likely not in the AJAX call or the update part of the Redux cycle, but in how your component connects to the state. Changes in the state should reactively trigger a re-render of the component, but for this to happen, the props of the component need to change. First, a few steps on how you might debug this issue, before some suggestions of possible causes, and suggested best practices.
Redux Dev Tools
Install Redux Dev Tools on Chrome. DevTools allow you to view a history of actions, and the changes each action induced to the Redux state.
First, use Redux DevTools to ensure that the shape of your Redux state is what you expect, even before triggering any action / AJAX call.
Then, select your AJAX fetch success (FETCH_ALL) action in Redux DevTools and, looking at the diff, see that the state changes as you expect.
Manual debugging
Try putting a debug value in the initialState of your reducer, and ensure it renders on the component.
See that the props of the component are updated when state changes. Put a console.log for this.props and nextProps in the life-cycle method componentWillReceiveProps(nextProps).
Issues to look for
Your component may be looking for the data in a wrong place of the state. The structure of your store follows the composition of your reducers. For example, the following puts the output of myDataReducer under app.db:
const rootReducer = combineReducers({
app: combineReducers({ db: myDataReducer }),
});
You seem to be using Immutable.js. Vanilla Redux treats the state as a POJO. You should either A) use a JavaScript object as the root of your state, or B) use a special combineReducers from redux-immutable.
Best practices
Trigger data fetching from componentDidMount instead of componentWillMount.
Trigger all side effects with an Redux action, instead of chaining promises directly in component code. Effect those changes using Redux middleware, like redux-saga. In your case, you could use redux-promise, create an action like
const action = ({type: FETCH_DATA, payload: axios.get(ENDPOINT, params)});
The redux-promise middleware handles the resolving, so that your reducer will only see the resolved value in the payload.
You're dispatching type 'FETCH_ALL' and catching case FETCH_ALL. One is a variable and one is a string.
Related
I am trying to understand someone else their code but have difficulty understand the interaction between Redux and React.
On a React page, I invoke a Redux action called getSubscriptionPlan. Inside that Redux action, I see it is able to load the correct data (point 1 below). This uses a reducer, in which I can again confirm the correct data is there (point 2 below).
Then the logic returns to the React page (point 3 below). I now would expect to be able to find somewhere in the Redux store the previously mentioned data. However, I can't find that data listed anywhere... not in this.state (where I would expect it), nor in this.props. Did the reducer perhaps not update the store state...?
What am I doing wrong and how can I get the data to point 3 below?
React page:
import { connect } from "react-redux";
import { getSubscriptionPlan } from "../../../appRedux/actions/planAction";
async componentDidMount() {
let { planId } = this.state;
await this.props.getSubscriptionPlan(planId);
// 3. I can't find the data anywhere here: not inside this.state and not inside this.props.
this.setState({plan: this.state.plan});
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.payment.paymentData !== this.props.payment.paymentData) {
this.setState({
checkout: this.props.payment.paymentData,
plan: this.props.payment.paymentData.plan,
});
}
}
const mapStateToProps = (state) => {
return {
plan: state.plan,
};
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators(
{ getSubscriptionPlan }, dispatch
);
};
export default withRouter(
connect(mapStateToProps, mapDispatchToProps)(Checkout)
);
Redux action:
export const getSubscriptionPlan = (id) => {
let token = getAuthToken();
return (dispatch) => {
axios
.get(`${url}/getSubscriptionPlan/${id}`, {
headers: { Authorization: `${token}` },
})
.then((res) => {
if (res.status === 200) {
// 1. From console.log(res.data) I know res.data correctly now contains the data
return dispatch({
type: GET_PLAN_SUCCESS,
payload: res.data,
});
})
};
};
Reducer:
export default function planReducer(state = initial_state, action) {
switch (action.type) {
case GET_PLAN_SUCCESS:
// 2. I know action.payload, at this point contains the correct data.
return { ...state, plan: action.payload };
default:
return state;
}
}
You are getting tripped up on how Redux works.
Redux does not use react component state. It manages state separately, and passes that state to components as props. When you call getSubscriptionPlan, you asynchronously dispatch an event to Redux, which handles the event and updates store state in the reducer. This state is the passed to the connected components mapStateToProps function, mapped to props, and then passed as props to your component. Passing new props triggers a componentDidUpdate and a rerender of the component.
A few key things here.
Redux does not interact with component state unless you explicitly set state with props passed from Redux.
Redux is asynchronous. That means that when you make a change to state via dispatch, the change is not immediately available in the component, but only available when new props are passed. It's event driven, not data binding. As a result, in your code you woun't see the plan prop in componentDidMount because at the time componentDidMount the call to getSubscriptionPlan hasn't happened.
You should see the prop populated in this.props in componentDidUpdate and in render before the didUpdate.
When working with react, it's best to think of components as basically functions of props with some extra lifecycle methods attached.
Good evening,
I am making weather app with react and redux. I have my reducer with a object of my data info with empty string etc:
const initState = {
city: '',
temp: '',
wind: ''
}
And for example in React I am gonna fetch all the data and what should i do now?
I should have dispatch action (FILL DATA - for example) and then i should make a [useState] inside my component for temporary place for my data. Then fill the data in my temporary place and then useDispatch to update redux store?
Actually, there are a few ways to do this, such as:
Use a redux middleware like redux-saga or redux-thunk, which I recommend.
In the request action, make the asynchronous call and then dispatch the success action to update the state data.
Call the API from the component and call the state redux store update action to update the global state. NOT RECOMMENDED.
Apparently, you're trying to use the third way, but it's not recommended because it beats the purpose of abstract redux and making the data scattered all around. A bad practice.
A middleware example would be very long, so I'll try to explain the second way briefly.
In your request action, do something like this:
...
axios.get(url).then(res => {
dispatch({ type: 'REQUEST_SUCCESS', data: res.data });
});
In your reducer:
...
switch (action.type) {
case: 'REQUEST_SUCCESS';
return { ...state, ...action.data };
}
There is a library called redux-thunk that will help you with this. It allows actions to be asynchronous, so you can actually dispatch the action, fetch all the data INSIDE the action and then dispatch an action to fill your state. After configuring it, your action would look something like this:
const getWeatherData = () => { // Declare it like any other action
return async (dispatch) => { //Then return a promise
const response = await getWeatherAPI(); // fetch from the backend
return dispatch({ // And then dispatch another action to populate your reducer
type: "FILL_DATA",
payload: response
})
}
}
This action would be dispatched from your code just like any other action, but considering it returns a promise you can actually await for it to finish and handle that any way you want.
I would like to create some middleware to check a tokens expires_at field refreshing the token if necessary and updating the state for the token fields.
I have used the redux toolkit to create the redux functionality, making use of slices. I have a slice that will update the state with token data and this works on the callback for the initial token.
When creating the middleware I can not get the slice to be called and therefore the state remains unchanged.
As a simple spike without an api call:
import { useSelector, useDispatch } from 'react-redux';
import { setTokens } from '../features/tokens/tokenSlice';
const refresher = () => ({ dispatch, getState }) => (next) => (action) => {
const exp_at = useSelector((state) => state.tokens.expires_at);
if(hasBreachedThreshold(exp_at)){
dispatch = useDispatch();
dispatch(
setTokens({
access_token: 'new token',
expires_at: 637423776000000000, -- hard coded date way in the future.
})
}
... else carry on.
);
I would expect when the middleware is called, on the first pass hasBreachedThreshold() returns true and the dispatch method would call the slice reducer and update the state. Any further runs would pass over as hasBreachedThreshold() would return false - for a while anyway.
What is happening though is that the hasBreachThreshold always returns false because the state is never updated, causing an indefinite loop.
The middleware is configured correctly and is called.
The expires_at value is extracted from the state.
hasBreachThreshold() is tested thoroughly and behaves correctly.
Being fairly new to React / Redux I expect my understanding of how and when to use dispatch is wrong. I assumed I could use dispatch similarly to how I do in my components, is this not the case? or am I going about this totally the wrong way?
When writing a middleware you have already the dispatch function and Redux store available through the function params:
// Use `dispatch` & `getState`
const refresher = () => ({ dispatch, getState }) => (next) => (action) => {
const exp_at = getState().tokens.expires_at;
if(hasBreachedThreshold(exp_at)){
dispatch(
setTokens({
access_token: 'new token',
expires_at: 637423776000000000, -- hard coded date way in the future.
})
}
);
Also you have essential mistake of when to use React hooks, refer to Rules of Hooks.
Hooks are not available in context of Redux middleware.
I have an action that I call to save a Brand that looks like this:
export function createBrand(props) {
return function(dispatch) {
postData('brands', props)
.then(response => {
dispatch({
type: CREATE_BRAND_SUCCESS,
payload: response
});
browserHistory.push("/brands/" + response.data.id);
}).catch(err => {
dispatch({type: CREATE_BRAND_ERROR});
});
}
}
This is called from a component. My question is around the browserHistory.push("/brands/" + response.data.id); which takes the user to the edit page for the brand they just saved. Is this the appropriate way/place to do this? Should I be responding to the CREATE_BRAND_SUCCESS dispatch event in the component itself instead? If so, what would that look like?
There's nothing wrong with the approach you're taking. It looks like you're using redux-thunk. I don't think it's a best practice to respond to events in the component. The only ways to do that (that I can think of) would be to create some custom middleware for checking the action type, then calling a method on your component (please don't do this) or using your reducers to keep some state around in the component like responsesFromApi: [response1, response2].
The approach I like most is to use tools that let me kick off declarative effects in the reducer while keeping the reducer pure. Redux Loop and my own redux-funk enable this. I like this approach, because the answer to "what happens when this action is dispatched" can be found in one place (the reducers). And declarative effects are easier to test.
So the way you'd do this with redux-funk is:
// in component.js in mapDispatchToProps
dispatch({
type: REQUEST_CREATE_BRAND, ...brandObject
});
// in reducer
const requestCreateBrand = brandName => postData('brands', brandName).catch(() => {type: CREATE_BRAND_FAILURE})
const navigateToBrand = id => browserHistory.push(`/brands/${id}`)
...
case REQUEST_CREATE_BRAND:
call(action, [requestCreateBrand, [action.brandName]])
return {...state, isRequesting: true}
case CREATE_BRAND_SUCCESS:
call(action, [navigateToBrand, [id]])
return {...state, isRequesting: false, brands: {...state.brands, [action.brandId]: action.brand}
...
You can also create declarative effects using the call function in Redux Saga
I am wondering how folks using Redux are approaching their backend persistence. Particularly, are you storing the "actions" in a database or are you only storing the last known state of the application?
If you are storing the actions, are you simply requesting them from the server, then replaying all of them when a given page loads? Couldn't this lead to some performance issues with a large scale app where there are lots of actions?
If you are storing just the "current state", how are you actually persisting this state at any given time as actions happen on a client?
Does anyone have some code examples of how they are connecting the redux reducers to backend storage apis?
I know this is a very "it depends on your app" type question, but I'm just pondering some ideas here and trying to get a feel for how this sort of "stateless" architecture could work in a full-stack sense.
Thanks everyone.
Definitely persist the state of your reducers!
If you persisted a sequence of actions instead, you wouldn't ever be able to modify your actions in your frontend without fiddling around inside your prod database.
Example: persist one reducer's state to a server
We'll start with three extra action types:
// actions: 'SAVE', 'SAVE_SUCCESS', 'SAVE_ERROR'
I use redux-thunk to do async server calls: it means that one action creator function can dispatch extra actions and inspect the current state.
The save action creator dispatches one action immediately (so that you can show a spinner, or disable a 'save' button in your UI). It then dispatches SAVE_SUCCESS or a SAVE_ERROR actions once the POST request has finished.
var actionCreators = {
save: () => {
return (dispatch, getState) => {
var currentState = getState();
var interestingBits = extractInterestingBitsFromState(currentState);
dispatch({type: 'SAVE'});
window.fetch(someUrl, {
method: 'POST',
body: JSON.stringify(interestingBits)
})
.then(checkStatus) // from https://github.com/github/fetch#handling-http-error-statuses
.then((response) => response.json())
.then((json) => dispatch actionCreators.saveSuccess(json.someResponseValue))
.catch((error) =>
console.error(error)
dispatch actionCreators.saveError(error)
);
}
},
saveSuccess: (someResponseValue) => return {type: 'SAVE_SUCCESS', someResponseValue},
saveError: (error) => return {type: 'SAVE_ERROR', error},
// other real actions here
};
(N.B. $.ajax would totally work in place of the window.fetch stuff, I just prefer not to load the whole of jQuery for one function!)
The reducer just keeps track of any outstanding server request.
function reducer(state, action) {
switch (action.type) {
case 'SAVE':
return Object.assign {}, state, {savePending: true, saveSucceeded: null, saveError: null}
break;
case 'SAVE_SUCCESS':
return Object.assign {}, state, {savePending: false, saveSucceeded: true, saveError: false}
break;
case 'SAVE_ERROR':
return Object.assign {}, state, {savePending: false, saveSucceeded: false, saveError: true}
break;
// real actions handled here
}
}
You'll probably want to do something with the someResponseValue that came back from the server - maybe it's an id of a newly created entity etc etc.
I hope this helps, it's worked nicely so far for me!
Definitely persist the actions!
This is only a counterexample, adding to Dan Fitch's comment in the previous answer.
If you persisted your state, you wouldn't ever be able to modify your state without altering columns and tables in your database. The state shows you only how things are now, you can't rebuild a previous state, and you won't know which facts had happened.
Example: persist an action to a server
Your action already is a "type" and a "payload", and that's probably all you need in an Event-Driven/Event-Sourcing architecture.
You can call your back-end and send the actions inside your actionCreator (see Dan Fox's answer).
Another alternative is to use a middleware to filter what actions you need to persist, and send them to your backend, and, optionally, dispatch new events to your store.
const persistenceActionTypes = ['CREATE_ORDER', 'UPDATE_PROFILE'];
// notPersistenceActionTypes = ['ADD_ITEM_TO_CART', 'REMOVE_ITEM_FROM_CART', 'NAVIGATE']
const persistenceMiddleware = store => dispatch => action => {
const result = dispatch(action);
if (persistenceActionTypes.indexOf(action.type) > -1) {
// or maybe you could filter by the payload. Ex:
// if (action.timestamp) {
sendToBackend(store, action);
}
return result;
}
const sendToBackend = (store, action) => {
const interestingBits = extractInterestingBitsFromAction(action);
// déjà vu
window.fetch(someUrl, {
method: 'POST',
body: JSON.stringify(interestingBits)
})
.then(checkStatus)
.then(response => response.json())
.then(json => {
store.dispatch(actionCreators.saveSuccess(json.someResponseValue));
})
.catch(error => {
console.error(error)
store.dispatch(actionCreators.saveError(error))
});
}
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk';
createStore(
yourReducer,
aPreloadedState,
applyMiddleware(thunk, persistenceMiddleware)
)
(You can also use a middleware to send current state to the backed. Call store.getState().)
Your app already knows how to transform actions into state with reducers, so you can also fetch actions from your backend too.