Redux using multiple reducers - reactjs

I'm making a react/redux app that shows recipes. When the app loads I load the first 10 from all users, like a feed. I want to make a page for an individual user that shows all their recipes.
I tried changing the redux store to return all user recipes when I visit their page, but when I go back to the main feed I have to change the store again (first 10 from all users). This changing from 'GET_RECIPES' to 'GET_USER_RECIPES' is causing me problems because since I am loading 10 at a time I need to return ...state + the newly loaded recipes.
Is it bad practice to make a new reducer which just shows user recipes. This seems like my only option. So I would have a reducer for my main feed and then one for when I click on a user page and shows all recipes by user id.
This is my current reducer that isn't working:
const recipesReducer = (state = [], action) => {
switch (action.type) {
case 'GET_RECIPES':
return [...state, ...action.payload]; //PROBLEMS
case 'POST_RECIPE':
return [...state, action.payload];
case 'GET_USER_RECIPES':
return [...action.payload]
default:
return state
}
}
export default booksReducer;

You could split your reducer state up in an object to be something like:
const initialState = {
recipes: [],
userRecipes: []
}
Then your reducer could work like so:
const recipesReducer = (state = initialState, action) => {
switch (action.type) {
case 'GET_RECIPES':
return {...state, recipes: action.payload};
case 'GET_USER_RECIPES':
return {...state, userRecipes: action.payload}
default:
return state
}
}
However, lets introduce you to a different approach. It's called normalising your Redux state. The Redux documentation explains this in detail with good examples. Essentially, because the type of data you would typically have in your recipe application is relational/nested you try to organise part of your redux state as if it were a database. Each type of data, i.e. chefs or recipes, would have its own section in your state. This helps to keep your data structure 'flat'.
If you are fairly new to React/Redux and this is just an experimental app or you don't mind perhaps a few more api calls then stick with the first option. State normalisation is very powerful and definitely worth learning about but not always the best option depending on your use case.

Related

Component isn't reloading after deleting an object from the state, I'm using React-redux

I'm working on an online streaming app where the user can create, edit, delete, and host streams. The problem is when I try deleting the stream, it deletes it in the database but my main component where the streams are being displayed doesn't reload, although it reloads itself when the user creates or edits the stream.
Here's my action creator:
export const deleteStream = (id) => async dispatch => {
await streams.delete(`/streams/${id}`);
dispatch({ type:'DELETE_STREAMS', payload: id });
history.push('/');
}
And here's the reducer:
const streamReducer = (state = {}, action) => {
switch(action.type){
case 'DELETE_STREAM':
return {...state, [action.payload]: undefined};
//I also used lodash to delete it alternatively as- return _.omit(state, action.payload);
default:
return state;
}
}
Also, not to forget that the objects are key interpolated in my server i.e., instead of having an array of objects, I have an object of objects.
Plural problem! DELETE_STREAMS vs DELETE_STREAM. You dispatch the former and reduce on the latter.
This is why it's always a good idea to have your actions defined somewhere even of its just export const X = "X". Then always reference them instead of using string literals.
It wasn't an error updating your React rendering, but that redux was never updated. A great tool to debug this is redux devtools https://github.com/reduxjs/redux-devtools, you can see the state of redux and every dispatched action and it's impact.

How to share/split a Redux Store between multiple generic components?

I have a generic component called "VendorResults". I am passing a string prop down to each of these generic components such as "Microsoft", "Apple", etc.
<ScrollView>
<SearchResults/>
<VendorResults vendor={"microsoft"}/>
<VendorResults vendor={"oracle"}/>
</ScrollView>
Within this generic component, I am passing the vendor prop as a parameter to my Redux-Thunk actions as such:
componentDidMount() {
const {vendor} = this.props;
this.props.getVendorInformation(vendor);
}
An API call kicks off, and Thunk actions are dispatched. The data eventually makes its way to the Reducer and store. However, When I have more than one generic Vendor component, whichever async call finishes last, appears to take precedent over all the others. For example, if oracle finishes loading last, the microsoft component's state will change and show oracle data.
Actions
export function getVendorInformation(vendor) {
const url = `${VENDOR_URL}api/search/${vendor}`;
return dispatch => {
dispatch(getVendor());
fetch(url)
.then(blob => blob.json())
.then(data => {
dispatch(getVendorSuccess(data))
})
.catch(e => {
console.log(e);
dispatch(getVendorError(e.message))
});
};
Reducer
export default function(state=initialState, action){
switch (action.type){
case FETCHING_VENDOR: return {payload:[], fetching: true}
case FETCH_VENDOR_SUCCESS: return {payload: action.payload.data}
case VENDOR_ERROR: return {payload:[], error: true, ...state}
}
return state;
}
My Question:
I want to maintain this pattern of generic/reusable Vendor components - I do not want a new component for each vendor. The same goes for actions/reducers; unique vendor actions/reducers would get messy.
How can I share/split/partition a single Redux store into vendor specific chunks to maintain seperation of state but still benefit from one flow. Thank you!!
You need to pass vendor to reducer via action and re-do structure of your state. If list of vendors is pre-determined and not very long, it probably will be less messy to just create separate actions/reducers.
Otherwise, you need to have nested reducer:
const supportedActions = [FETCHING_VENDOR, FETCH_VENDOR_SUCCESS, VENDOR_ERROR];
const initialVendorState = {data:[], fetching: false, error: false};
const vendorReducer = (state = initialVendorState, action) => {
switch (action.type){
case FETCHING_VENDOR: return {data:[], fetching: true}
case FETCH_VENDOR_SUCCESS: return {data: action.payload.data}
case VENDOR_ERROR: return {...state, data:[], error: true}
}
return state;
}
const reducer = (state = {}, action) => {
if (supportedActions.includes(action.type)) {
const s = {};
s[action.payload.vendor] = vendorReducer(state[action.payload.vendor], action);
return {
...state,
...s
};
}
return state
}
export default reducer;
And your action creators should take vendor as parameter and pass it to reducer:
const fetchVendorSuccess = (vendor, data) => ({
type: FETCH_VENDOR_SUCCESS,
payload: {
vendor,
data
}
});
In your connect function you will need to use smth like data: (state[vendor] || {}).data to avoid errors if state does not have any info about that vendor
However, When I have more than one generic Vendor component, whichever async call finishes last, appears to take precedent over all the others. For example, if oracle finishes loading last, the microsoft component's state will change and show oracle data.
You are seeing Oracle data because after fetching the vendor data you are overwriting the entire vendor state with the latest array of vendor items.
case FETCH_VENDOR_SUCCESS: return {payload: action.payload.data}
To avoid this, you would need to merge the previous state with the new state.
Solution depends on what each vendor response looks like. As Gennady suggested, you can use an object and make each vendor a property on the object.
Using a flat array to store all the different vendor items presents challenges. How would you determine if a vendor has already been fetched?
To avoid overwriting the previous vendor, you would need to merge the new state with previous state. E.g.
case FETCH_VENDOR_SUCCESS: return [...state.data, ...payload.data]

How can a state be cleared when moving away from a page

I have created a Quiz app that tracks the correct answers in state called correct_answer. The issue is when the user leaves one quiz and moves on to the next one, The quiz answers are still stored from the last quiz.
I have tried using LOCATION_CHANGE from react-router-redux, but I am not sure I am using it correctly.
import { LOCATION_CHANGE } from "react-router-redux";
const initialState = {
questions: [],
answers: [],
correct_answer: []
};
export default function(state = initialState, action) {
switch (action.type) {
case "GET_QUESTIONS":
return { ...state, questions: action.payload };
case "GET_ANSWERS":
return { ...state, answers: action.payload };
case "CORRECT_ANSWER":
return {
...state,
correct_answer: [...state.correct_answer, action.payload]
};
case LOCATION_CHANGE:
return {state = initialState};
default:
return state;
}
}```
The app needs to clear the correct_answers state anytime the user moves away from the page.
Keep in mind that the redux store is an omnipresent data structure. The data persists regardless of any ui changes in your app, which includes local state changes in a component and mounting/unmounting components (unless you tear down your reducer, but that's not what you're doing at all).
As mentioned in the comments, it's your job to clear your state. Create an action that will reset the reducer. How you implement it is based on your exact implementation of your Quiz component(s).
How does mounting/unmounting/prop changes work when you switch quizes? Are you mounting an entirely new component or are you feeding new data into an existing component?
If the next quiz is an entirely new instance, then you call it when you unmount the prior quiz:
componentWillUnmount() {
this.props.resetQuizState() // your action that resets the data in your store
}
If it is the same component but new props are passed in:
handleNextQuizClick() {
this.props.resetQuizState()
// and then rest of data manipulation/calling/parsing
}
render() {
return (
<button onClick={this.handleNextQuizClick}>
next quiz
</button>
}

React-redux - state overwrites itself

I am using react-redux (for the first time). I have a component into which users put a 'startDate' and an 'endDate'. These should then be stored in the redux store, so that they persist.
I have the following action creator:
export const setDates = dates => ({
type: "SET_DATES",
payload: dates
});
The following reducer:
const dates = (state = {}, action) => {
switch (action.type) {
case "SET_DATES":
return action.payload;
default:
return state;
}
};
export default dates;
The state is set conditionally (i.e. only if the start and end dates actually make sense) like this:
handleSubmit = () => {
if (this.state.startDate <= this.state.endDate) {
store.dispatch(setDates([this.state.startDate, this.state.endDate]));
window.location = `/search/${
this.state.location
}&${this.state.startDate.format("DDMMYYYY")}&${this.state.endDate.format(
"DDMMYYYY"
)}&${this.state.guestCount}&${this.state.offset}&${this.state.count}`;
} else {
console.log("HANDLE ERROR");
}
};
The problem, according to the chrome redux dev-tools, is that when the submit is triggered, the store does indeed change to the new dates, but it then seems to be immediately overwritten to the empty state. By modifying the reducer to take state = {dates: 'foo'} as its first argument, I can get the store to persist 'dates:foo'. This suggests to me that, for some reason, the reducer is being called twice - once with an action of type "SET_DATES", which works, and then again, immediately, with an action of unknown type (confirmed by console.log-ging action.type), which causes it to return the default state.
So I'm pretty sure I know what the problem is, but I have no idea why it would do this.
I Already commented, but anyways. The problem is that you reload the page. It reloads redux, and it boots up from initial state, which is probably an empty array. Here is a great video from one of the brains behind redux.
https://egghead.io/lessons/javascript-redux-persisting-the-state-to-the-local-storage
It all boils down to subscribing to the store state changes, and saving it / loading the state back from storage of your choise.
Try changing you reducer like this
const dates = (state = {}, action) => {
switch (action.type) {
case "SET_DATES":
return Object.assign({}, state, {
action.payload
});
default:
return state;
}
};
export default dates;

How to highlight multiple selection in react using redux?

I am trying to make multiple selection and highlight them. So suppose if I am displaying 5 buttons and if user clicks on a button it will be higlighted and if the user clicks on it again then it will become normal.
I am using redux store the state of my button. These button are getting created dynamically based on how many they are.
Redcuer
CurrentReducer(state = {court:{}}, action){
switch (action.type){
case 'COURT_SELECTED':{
return {...state,
court: action.payload
}
}}
Dispatcing action on onClick
this.props.dispatch(actions.selected({type:'COURT_S', payload: id}))
I had thought of storing id's in court by making it as an array or storing id with true or false in an object.
can anyone give me a simple working solution which is super easy
You can do something like this:
reduser
const selectedCourts = (state = {}, action) => {
switch (action.type) {
case 'COURT_TOGGLE': {
return {
...state,
[action.id]: !state[action.id]
};
}
}
return state;
};
action
dispatch({type: 'COURT_TOGGLE', id});
So you'll have piece of state like this:
selectedCourts: {
1: true,
2: false,
3: false,
...
}
I recommend you to use combineReducers to split your reducer into smaller parts so each subreducer would handle a simple part of your state like an array or one level object or boolean variable.
CurrentReducer.js
export default combineReducers({
selectedCourts,
...
});

Resources