ReactJS+Redux: How to handle a prop that depends on another props - reactjs

I am creating a Graph a React.Component. When componentWillMount, this graph will be in charge of loading a configuration file using an Async Actions. Configuration file also contains a query information that needs to be fetched.
For now, I am chaining the two requests (configuration + query) and store their results in the current Redux state.
{
configurations: [{
"my-configuration":
{
// information about the configuration
query: "my-query"
}
}],
queries: [{
"my-query" : {
// information about the query
}
}]
}
I'd like my Graph component to be connected to both variables. However, I am not aware of the query name before the configuration is fetched.
const mapStateToProps = (state, ownProps) => ({
configuration: state.getIn([
"configurations",
ownProps.configurationName
]),
query: state.getIn([
"queries",
queryName // the name of the query comes is known in the configuration
]),
});
I might face a design issue, but I wanted to have your feedback. How would you deal this situation ?
For now, I created a state for the component but it would need to be in sync with the redux state.
Environment
react#15.3.1
redux#3.6.0
redux-thunk#2.1.0

Edit: what if myConfig is null and waiting to be fetched from server?
Selector becomes
const mapStateToProps = (state, ownProps) => {
const myConfig = state.getIn([
"configurations",
ownProps.configurationName
]);
return {
configuration: myConfig,
query: myConfig ? state.getIn([
"queries",
myConfig.queryName
]) : *defaultQuery*,
};
};
And you should handle getting myConfig in async action.
const getMyConfig = (...args) => (dispatch) => {
dispatch(GET_MY_CONFIG_REQUEST);
api.getMyConfig(...args)
.then((res) => dispatch({ type: GET_MY_CONFIG_SUCCESS, res }))
.catch((err) => dispatch({ type: GET_MY_CONFIG_FAIL, err }));
}
And in reducer need to update myConfig upon GET_MY_CONFIG_SUCCESS action
...
case GET_MY_CONFIG_SUCCESS:
return { ...state, myConfig: action.res };
...
Original Answer
something like this?
const mapStateToProps = (state, ownProps) => {
const myConfig = state.getIn([
"configurations",
ownProps.configurationName
]);
return {
configuration: myConfig,
query: state.getIn([
"queries",
myConfig.queryName
]),
};
};

Reading this nice article about things you should know about Redux, here is what you will find:
Denormalizing data in mapStateToProps is normal. When I first started using Redux, I wasn’t sure if it was kosher to do “computation” in mapStateToProps functions. I didn’t see examples of this in the Redux repo or docs. It took a few weeks for me to find somebody else using a “selector” in mapStateToProps. You have no idea how excited I was to discover somebody else doing this!
Hope this could help someone else!

Related

Lookup multiple Firebase entries using Redux action

I have this Redux action, which looks up a user and returns all the items they have:
export const itemsFetch = () => {
const { currentUser } = firebase.auth();
return dispatch => {
firebase
.database()
.ref(`users/${currentUser.uid}/items`)
.on('value', snapshot => {
dispatch({ type: ITEMS_FETCH_SUCCESS, payload: snapshot.val() });
});
};
};
Works great, and each item returned has a unique key associated with it.
I want to modify this action to look up specific items, which I've done. That works fine too:
export const itemLookup = uid => {
const { currentUser } = firebase.auth();
return dispatch => {
firebase
.database()
.ref(`users/${currentUser.uid}/items/${uid}`)
.on('value', snapshot => {
dispatch({ type: ITEM_LOOKUP_SUCCESS, payload: snapshot.val() });
});
};
};
This also works fine, but I can only use this to lookup a single item.
I want to loop over an array of item ids, and lookup details for each. Doing this from a component, and using mapStateToProps, causes the component to rerender each time, losing the previous lookup in the process.
Is it best to loop over the ids I have at a component level, and make multiple calls. Or should I pass the array to the action, and somehow loop over them within the action?
Thanks
I feel like I'm doing something dumb, or misunderstanding Redux completely.
In my opinion, this is one of the few limitations that firebase has (along side with queries) that sometimes make me want to grow hair again and lose it (I am bald).
I am more experienced with Firestore although I have used Database, but I think you are correct that you can only request one item in Firebase. What I would do to solve this, is to create a new action that receives an array of IDs and then executes and array of promises that will query each doc.
Something like (pseudo code, and you might need to wrap your firebase call into a promise):
let promises = [];
arrayIds.forEach(id => {
promises.push(firebase.database().ref.get(id))
})
return Promise.all(promises).then(dispatch(results))
Now, if you find that the amount of results are usually not a lot, it is totally fine (and usually the way Firebase requires you to) to complete the data filtering in the client.
Using the response from sfratini, I managed to work it out. Here's the action:
export const itemLookup = oid => {
const { currentUser } = firebase.auth();
const items = []
return dispatch => {
new Promise(function (res, rej) {
oid.forEach(id => {
firebase.database().ref(`users/${currentUser.uid}/items/${id}`).on('value', snapshot => {
items.push(snapshot.val())
})
})
}).then(dispatch({ type: ITEM_LOOKUP_SUCCESS, payload: items }))
}
I used the same reducer, and now the items array makes it down to component level. Perfect!

Redux - Reducer not being called

Objective
I am trying to pass some objects I get back from Firestore into my reducer so I can display some results back to the user.
Problem
When I try to call and pass the query to the reducer it does not appear to work. I am running a console.log to see if the reducer gets called but nothin it appearing. I think is because I have nested return statements?, Is this true?
Here is my action:
export const queryBidder = (value) => {
return async (dispatch, getState, { getFirestore }) => {
const firestore = getFirestore();
const normalizeValue = _.capitalize(value);
let Query = []
firestore.collection("bidders").where("firstname", ">=", normalizeValue).get()
.then(snapshot => {
if (snapshot.empty) {
console.log('No matching bidders.');
return;
}
snapshot.forEach(doc => {
Query.push(doc.data());
return { type: actionTypes.QUERYBIDDER, Query: Query };
});
})
.catch(err => {
console.log('Error getting documents', err);
});
}
};
When I put the return statement above the async return statement, all works well. However, I need to be able to call getFirestore(). This setup comes from a tutorial I found on Udemy, to be honest I don't quite understand it.
return async (dispatch, getState, { getFirestore })
How are these getting passed in? Where do they come from? Why does the order matter? How can I just access the getfirestore() function without wrapping my actions logic in this function?
I am unsure of why this worked but while awaiting for responses I opted to change from return to dispatch.
dispatch({ type: actionTypes.QUERYBIDDER, Query: Query });
this resolved my issue. Look for a more thorough answer of why this worked and my above original questions.
From redux documentation about using dispatch - This is the only way to trigger a state change. So basically if you want to change redux state you should dispatch an action with parameters - action type and new data.
You can read more about redux dispatch here

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

Best Way to load model into redux store on route load

I have a React app that uses React-Router/React-Router-dom for page navigation and redux to store some global state info (jwt token for django rest framework for example). The state also stores info about the currently viewed page, such as the serialized django model.
But what is the best way to load the django model into the redux store when the route changes? I'm having trouble wrapping my head around where logic should be going.
If you view the repo below you can see where I'm having trouble figuring it out.
In this example when someone navigates to /spells/:id, it should load the spell django model into the redux store so information about it is globally accessible.
But how do I go about doing that? Where do I call the actions and reducers to properly handle the state?
Any guidance would be appreciated.
You can view the full project here. The component in question here is LayoutSpellView (/frontend/src/components/LayoutSpellView). That's where the model information is stored, displayed, etc.
Edit: Adding relevant code
Called in componentDidMount:
axios
.get("http://localhost:3000/api/spells/" + spellId)
.then(response => {
let spell = Object.assign({}, spellView.state.spell);
spell.id = response.data.id;
spell.owner = response.data.owner;
...blahblah other fields
this.setState({
spell
});
})
.then(response => {
this.props.dispatch({
type: 'FETCH_SPELL_SUCCESS',
payload: this.state.spell,
});
})
.catch(function(error) {
console.error('[API]\t', error);
});
In LayoutSpellView (same component as above)
import {loadSpell} from "../src/reducers";
const mapStateToProps = (state) => ({
spell: loadSpell(state.spell.id),
});
const mapDispatchToProps = (dispatch) => ({
getSpell: (state.spell.id) => {
dispatch(loadSpell(state.spell.id))
}
});
Actions spell.js:
export const FETCH_SPELL = '##spell/FETCH_SPELL';
export const FETCH_SPELL_SUCCESS = '##spell/FETCH_SPELL_SUCCESS';
export const FETCH_SPELL_FAILURE = '##spell/FETCH_SPELL_FAILURE';
export const loadSpell = (spellId) => ({
[RSAA]: {
endpoint: '/api/spell/${spellId}',
method: 'GET',
types: [
FETCH_SPELL, FETCH_SPELL_SUCCESS, FETCH_SPELL_FAILURE
]
}
});
Reducers spell.js:
const initialState = {
spell: {
id: 0,
owner: 0,
Name: 'Name',
School: 'unknown',
Subschool: 'unknown',
}
};
export default (state=initialState, action) => {
switch(action.type) {
case spell_action.FETCH_SPELL_SUCCESS:
return {
spell: {
id: action.payload.spell.id,
owner: action.payload.spell.owner,
Name: action.payload.spell.Name,
School: action.payload.spell.School,
Subschool: action.payload.spell.Subschool,
}
};
default:
return state;
}
}
export function loadSpell(state) {
if (state) {
return state.spell
}
}
Let's look at the question in a different way. Instead of asking "How do I dispatch an action when routes change", let's ask "What is the actual source of truth: Redux or URL?"
If we go with redux being the Single Source of Truth, then that would mean that we need to dispatch some action that would cause some side-effect ( maybe redux-saga or redux-observable or even redux-thunk? ) that changed the url:
Comp -> dispatch(action) -> store updates -> URL changes
If we go with the URL being the Single Source of Truth, we change the flow to:
URL changes -> dispatch(action) -> store updates
If we go this route, which is what it sounds like you are wanting, you will need to probably hook up middleware, which are functions of the following signature:
store => next => action => next(action)
Depending on the router that you are using, you can either hook into their actions or you can hook into window.onpopstate and check the next url. Either way, the overall middleware function would look something like
const middleware = store => {
return next => action => {
if (actionWillCauseSpellToBeNeeded(action)) {
makeAPICall()
.then(transformAPIToAction)
.catch(transformError)
.then(store.dispatch)
}
return next(action)
}
}

react-redux not dispatching thunk api call

I'm taking a working web version with redux and Api calls and porting them to a React Native app. However I notice when trying to dispatch a thunk to make an API call, I can't seem to see a console log in my thunk to confirm the dispatch. This makes me think something is not connected properly but I just don't see what that is. What am I missing?
I create a store with an initial state: When I log store.getState() everything looks fine.
const initialState = {
config: fromJS({
apiUrl: "http://localhost:3000/account-data",
})
}
const store = createStore(
reducers,
initialState,
compose(
applyMiddleware(thunk),
)
)
I use mapDispatchToProps and I see the functions in my list of props
export function mapDispatchToProps(dispatch) {
return {
loadProducts: () => dispatch(loadProducts())
};
}
However, when I inspect my loadProducts function, I do not see a console log confirming the dispatch. What's going on here? Why is loadProducts not dispatching? On the web version I'm able to confirm a network request and logs. On React Native I do not see a network request or these console logs.
export function loadProductsCall() {
console.log('in RN loadProductsCall') //don't see this
const opts = constructAxpOpts();
return {
[CALL_API]: {
types: [
LOAD_REQUEST,
LOAD_SUCCESS,
LOAD_FAILURE
],
callAPI: (client, state) =>
client.get(`${state.config.get('apiUrl')}/members`, opts),
shouldForceFetch: () => false,
isLoaded: state => !!(state.core.resources.products.get('productsOrder') &&
state.core.resources.products.get('productsOrder').length),
getResourceFromState: (state) => state.core.resources.products.toJS(),
isLoading: state => !!state.core.resources.products.get('isLoading'),
getLoadingPromise: state => state.core.resources.products.get('loadingPromise'),
payload: {}
}
};
}
export function loadProducts() {
console.log('in loadProducts') //don't see this
return (dispatch) =>
console.log('in loadProducts dispatched 2') //don't see this either
dispatch(loadProductsCall())
.then((response) => {
return response;
});
}
This code is missing custom API middleware that handles three action types. Also, in mapDispatchToProps a function is wrapping the dispatch. This function need to either be unwrapped and return a promise or called somewhere else in the code.

Resources