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

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>
}

Related

TypeError: state.items is not iterable React-Redux

I'm knew in React-Redux and I'm constantly getting stuck in the same error again and again -
"TypeError: state.items is not iterable"
I tried all types of solutions but didn't succeed.
I have a simple app that has an input and 2 buttons - when the person clicks on the addItem button the input text gets added to my list in my global state and when he clicks the reset button - my list in my global state becomes empty.
the problem- the addItem button works just fine until after the reset button is clicked-the reset button resets the state and then when i try adding an item again it shows the error.
"TypeError: state.items is not iterable"
Here is my code in my reducer-
function manageList(state = { items: [] }, action) {
debugger
switch (action.type) {
case ADD_ITEM:
return { items: [...state.items, action.payload] };
case RESET_LIST:
return { item: [...state.items, []] };
default:
return state
}
}
export default manageList;
I thought maybe to change the RESET_LIST item to items but that disabled the reset button
import { ADD_ITEM, RESET_LIST, } from '../actions'
function manageList(state = { items: [] }, action) {
debugger
switch (action.type) {
case ADD_ITEM:
return { items: [...state.items, action.payload] };
case RESET_LIST:
return { items: [...state.items, []] };
default:
return state
}
}
export default manageList;
You have a typo there item instead of items.
Also, it should be
case RESET_LIST:
return { items: [] };
In general: the style of redux you are writing here is not the style of redux we are recommending people to learn if they learn redux nowadays, as redux has changed a lot over the last 1-2 years. You might be following an outdated tutorial. Please follow the official redux tutorials at https://redux.js.org/tutorials/index which will show you a lot more up-to-date approach that will lead to a lot less boilerplate and does not require you to work with immutable logic.

Confusion on how to properly use reducer in Redux

I was learning Redux and wanted to properly understand how reducer works. If you look at the reducer called titleReducer:
const initialState={
titleColor: null,
}
const titleReducer = (state = initialState, action) => {
switch (action.type) {
case TURN_TITLE_GREEN:
return {
...state,
titleColor: 'green'
}
case UPDATE_TITLE:
return {
...state,
title: action.payload
}
default: return state;
}
}
So, I wanted to ask some questions on the above code. Firstly, imagine that there are three reducers: reducer1. reducer2 and titleReducer(I know silly example but bear with me). The question is why even if state of titleReducer has only one property "titleColor", we need to create new make a copy of state by using ...state. What if we just use:
{
title: action.payload
}
The second question is ok every time titleReducer get "TURN_TITLE_GREEN" action, the reducer takes the previous state and creates a new copy and sends back to a component. The question is "where is the previous state is taken from? I mean, is it true that when titleReducer receives the action of TURN_TITLE_GREEN, it creates new copy and at the same time puts 'titleColor: 'green' into state which will serve as previous state next time. Is that true?"

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;

Redux action taking too long add one item to empty array - No Promise returned

I am using Redux to manage the state of my react app. I am creating an object, then passing this object to addTile function which is my action.
So my action.ts looks like this:
export function addTile(tile){
return {
type: "ADD_TILE",
payload: tile
}
}
My reducer.ts looks like this:
const reducer = (state = {
isPanelOpen: false,
isDiscardAllChangesOpen: false,
tiles: [],
tempTiles: [],
}, action) => {
switch (action.type) {
case "PANEL_VISIBILITY":
state = {
...state,
isPanelOpen: action.payload
};
break;
case "ADD_TILE":
state = {
...state,
tiles: [...state.tiles, action.payload]
}
break;
}
return state;
};
export default reducer;
However, if I try to use this in my component like this:
this.props.addTile(tile)
alert(this.props.tiles.length)
The length will be 0. However, the item is really added to the array, but at the time of the alert execution, the length was 0. From my reading on Redux docs, actions by default are async (or at least that's how I understand they are).
I even try to do this:
this.props.addTile(tile)
.then(response => { //some code})
Then I get cannot read property then of undefined.
Any ideas?
When you try to check the prop right after dispatching an action, React has not yet had a chance to re-render. It's not that it's "taking too long", it's that your own code is still executing. So, React has not re-rendered your component, and the prop value is still the same.
Your promise example would only work if addTile() was a thunk that returned a promise.

Redux using multiple reducers

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.

Resources