ReactJS: Page not being re-rendered on state change - javascript

I am learning react through the university of helsinki's full stack open course. The point of the code is there are two buttons and a quote shown on page. One button goes to a random quote and the other lets you put in a vote and shows how many votes that quote has total. The problem is that when I click vote, it adds the vote in the background but doesn't re-render the total amount of votes unless I change the quote.
I've tried different ways to go about conducting state change such as creating a function specifically for the setVote but I can't get it to work.
const App = () => {
const [selected, setSelected] = useState(0)
let [vote, setVote] = useState([...copyVote])
const changeAnecdote = () => {
setSelected(Math.floor(Math.random() * 6))
}
const addVote = () => {
copyVote[selected] = copyVote[selected] + 1
setVote(vote = copyVote)
}
return (
<div>
<Button onClick={changeAnecdote} text='Next Anecdote'/>
<Button onClick={addVote} text='Vote'/>
<div>
{anecdotes[selected]}
<DisplayVotes vote={vote} selected={selected}/>
</div>
</div>
)
}
copyVote is a copy of a zero-filled array and DisplayVotes simply shows how many votes total for that quote on screen.
When I check for changes in the array of votes after hitting vote through developer tools, the array doesn't change until I go to another quote.
Anyone have any ideas as to what I'm doing wrong?

There is a few things that should be updated here.
Firstly, copyVote is an array containing the number of votes for each quote. So ...copyVote will give you each item in that array. You only want the vote for the current quote. Your initial vote state should be
const [vote, setVote] = useState(copyVote[selected])
You also want to update the addVote function the way DSCH mentioned.
const addVote = () => {
// copyVote[selected]++ is the same as copyVote[selected] += 1 and copyVote[selected] = copyVote[selected] + 1
setVote(copyVote[selected]++)
}
Finally, you want to add a way to update the vote each time the anecdote is changed. You could do this in the changeAnecdote function, but a better approach would be to use an effect hook that is dependent on the selected state.
useEffect(() => {
// Set the value of vote to match the newly selected quote
setVote(copyVote[selected])
}, [selected])
Using this, the DisplayVotes vote prop is only going to display the vote for the currently selected quote. You may need to updated that component to handle this.

In setVote(vote = copyVote) the expression within the parentheses is an expression to set the vote variable with the value in copyVote. I assume that's why you use let in let [vote, setVote] = useState([...copyVote]), since probably you got an error setting a value to a const.
As setVote is what return from the useState what you probably want to do is:
const addVote = () => {
copyVote[selected] = copyVote[selected] + 1
setVote(copyVote[selected])
}

Related

Randomly order elements in an array Once in React (Possibly using Use effect)

I'm working on a solo project quizzical whilst learning react but cant find a solution to give the answers a random order without breaking my code. I have tried to use a shuffle function but this just causes the order of the answers to change with every click, I also tried to add the correct answer at a random index but the same problem happened. Not sure if I have to totally restructure my code or if there is a workaround. Be great if someone could take a look.
https://scrimba.com/learn/learnreact/fork-of-section-4-solo-project-coc8e46febb53e8d33018993e
I have attempted to use useEffect but really unsure if this is a situation where it should be used, this is just what i've left in my code. Removing the useEffect shuffles on every button click placing answers in the wrong places. I have included this code but may be hard to gauge the problem so scrim is attached
const incorrectIds = [nanoid(), nanoid(), nanoid()]
let counter = 0
const correctAnswer =
<button
value='correct'
id={correctId}
onClick={e => {
changeClass(correctId, 'correct')
props.answerQuestion(e, 'correct')
}}>
{props.result.correct_answer}</button>
let answers = props.result.incorrect_answers
let allAnswers = answers.map(answer => {
const id = incorrectIds[counter]
counter++
return (
<button
value='incorrect'
id={id}
onClick={e => {
changeClass(correctId, 'correct')
changeClass(id, 'incorrect')
props.answerQuestion(e, 'incorrect')
}}>
{answer}</button>)})
allAnswers.push(correctAnswer)
React.useEffect(function() {
allAnswers = shuffle(allAnswers)
}, [])```
I solved the problem by setting state of a randomly sorted array within use effect only changing when props.result changed
const [randomArray, setRandomArray] = React.useState([])
React.useEffect(function() {
setRandomArray(shuffle(allAnswers))
}, [props.result])

React - filtering items from an array in any order

I'm sorry that this question has been asked in a similar fashion. I've read through a lot of similar threads but just don't know enough to apply it to my little project. I'm very new to React so I would appreciate any help!
Getting to it, I'm making a pokemon game where someone can hit click buttons to filter down a JSON list of pokemon. They can go by type,weaknessess,etc.
But I'm a little confused in keeping a 'global array' if that's the right word. It may be something I don't understand with React's useState.
Here's my code
import React, { useState, useEffect } from "react";
import "./App.css";
import PokemonLibrary from "./data/PokemonList.json";
export default function App() {
const [pokemonData, setPokemonData] = React.useState(PokemonLibrary.pokemon);
const pokeListCopy = PokemonLibrary.pokemon;
const filterTypeOne = () => { //filter type
const myFilteredPoke = pokeListCopy.filter((pokeType) => {
return pokeType.type.includes("Grass");
});
console.log(myFilteredPoke); // shows array of objects of left over pokemon
setPokemonData(myFilteredPoke);
};
const filterWeakness = () => { //filter weakness
const myFilteredPoke = pokeListCopy.filter((pokeType) => {
return pokeType.weaknesses.includes("Ice");
});
setPokemonData(myFilteredPoke);
};
return (
<div className="App">
<h1>Pokemon Selector!</h1>
<div>
<button onClick={filterTypeOne}>Filter Grass</button>
<button onClick={filterWeakness}>Weak to Ice</button>
</div>
{pokemonData &&
pokemonData.map((poke) => (
<p key={poke.id}>
#{poke.num} | {poke.name} | {poke.type[0]} {poke.type[1]}
<img src={poke.img} alt="Pokemon Images"></img>
</p>
))}
</div>
);
}
My question is, how do I keep a consistent array for these two functions (and more) to pull the same data from? I'd like it to be able to be filtered in either order. But currently, these filter separate arrays. I've played with it a lot using normal JavaScript, but I can't quite figure this out.
I hope that was enough information. Please let me know if this didn't make sense! I'd appreciate any guidance. Thank you.
Problem
You are facing this problem because you try to set the state of the list in an "imperative" manner, but React is meant to be used more "declarative". That means, what you try here is like:
"if I click the button, change the list to contain the items that contain 'Grass'",
but how you should use React is:
"if I click the button, then the state of my component should be that the list only contains items with 'grass'"
That might sound like the same, but there is a subtle difference. React is good in changing a state dependent on some other state. You might say "but that's what I'm trying to do, changing the state of the list", but then you have tell the full story
"if I click the button, and the list is not filtered already, and maybe contains the items X, then change the list to contain the items that contain 'Grass', unless ..."
This becomes quite complicated, especially comparing contents of lists and components.
Solution
There are different solutions to your problem, but what you should do is basically something like:
set the component state to describe what you want
have other parts of your program (and React) take care to give you a the list dependent on this description
e.g.:
const [pokemonData, setPokemonData] = React.useState(PokemonLibrary.pokemon);
const [listState, setListState] = React.useState({ type: '', weekness: '' });
useEffect(() => {
let newList = pokemonData;
if( listState.type === 'Grass' ){
newList = newList.filter( ... );
}
if( listState.weekness === 'Ice' ){
newList = newList.filter( ... );
}
setPokemonData(newList);
}, listState );
return (
<div>
<button onClick={()=>{ setListState({ ...listState, type: 'Grass' }) }}>Filter Grass</button>
{ pokemonData.map( (poke) => ... ) }
</div>
);
(This code is not very elegant and would not even work, and should only illustrate the idea. From here on there are several ways how to implement the filtering mechanism)

react useState doesn't update state

I have a table with checkboxes and I want to save selected checkboxes' ids in state. So here's my code.
Input looks like this (I use coreui so this is the inside of a table's scopedSlots):
selected: (item) => {
return (
<td style={{ width: '40px' }}>
<CInputCheckbox
className="mx-auto"
id={item.id}
onChange={(e) => handleSelect(e)}
/>
</td>
);}
And this is the rest:
const [selectedRows, setSelectedRows] = useState([]);
const handleSelect = (e) => {
const id = e.target.id;
const index = selectedRows.indexOf(id);
const rows = [...selectedRows];
if (index === -1) {
rows.push(id);
} else {
rows.split(index, 1);
}
setSelectedRows(rows);
};
And the weirdest thing happens - in the chrome's react devtools I see that the first id is being added to the selectedRows array and then when I select another row - the previous item in the array is being overwritten. When i console.log my selectedRows array it shows empty array always (even if I see in the devtools that there's one item). I have no idea what I'm doing wrong here.
There is not a lot of information to work with in your question, so I had to make many assumptions about how are they suppose to work, but this is what I got so far:
the handleSelect function has a little error when it comes to removing items from the list, your function is using split but I think what you meant was slice
I think the scopes of handleSelect and const [selectedRows, setSelectedRows] = useState([]); are wrong. For what I can see, they are at the same level of selected component, which will fail as they will only be able to keep track of one selected component at the time, and I think you are needing to keep track of multiple selected components.
Here is a working version of what I think you are trying to do
https://codepen.io/richard-unal/pen/eYWXXOo?editors=1111
If you need clarification on something, please let me know, I'm aware that I'm not the best explainer.

Error: Too many re-renders. React limits the number of renders to prevent an infinite loop. - React

So I'm trying to make a screen where data from user's localstorage is used (Lets call it var1) but I'm getting Error: Too many re-renders. React limits the number of renders to prevent an infinite loop. error. What I'm trying to do is check if the data from user's localstorage exists and if it does then it will put that data into a state but first it will grab another variable from localstorage (User auth token, lets call it var2) and put it into every object in var1 (var1 is a list which contains objects) and this is done using map then the state is set to the changed var1 with the auth token(or var2), then it returns some HTML and some logic is used in HTML, For every object in var1 it will create a new select tag with numbers ranging from 1 to 20 and this is done using mapping an array with 20 numbers (I'm doing this because I could not get for loop to work properly) and if the current number of option in select tag matches a key value pair in one of var1's object then it will
select the option tag or put selected attribute on option tag and if you change the value of select tag then it will trigger a function which will map through var1 and select the object which user requested and change the value of quantity to whatever the user selected on select tag. I tried to cut down and simplify my code as much as I could. My code is like this:
function RandomScreen() {
const [var1, setvar1] = useState([])
let localstoragevar = JSON.parse(localStorage.getItem('var'))
let newCart = []
if (localstoragevar) {
localstoragevar.map(item => {
item.authtoken = localStorage.getItem('AuthToken')
newCart.push(item)
})
}
setvar1(newCart)
let twenty = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
return (
{var1.map(newItem => {
{/* HTML code goes here */}
{twenty.map(number => {
if (number == item.quantity) {
return (
<option onChange={handleClick} selected name={newItem.id} value={newItem.quantity}>{newItem.quantity}</option>
)
} else {
return (
<option onChange={handleClick} name={newItem.id} value={number}>{number}</option>
)
}
})}
})}
)
}
Your render calls setvar1 which in turn trigger's a re-render.
You should put this whole logic inside a useEffect hook:
useEffect(() => {
let localstoragevar = JSON.parse(localStorage.getItem('var'))
let newCart = []
if (localstoragevar) {
localstoragevar.map(item => {
item.authtoken = localStorage.getItem('AuthToken')
newCart.push(item)
})
}
setvar1(newCart)
}, []);
This is what you have to do is to avoid logic in your render function. For that case, we do have useEffect function plus on top of that you may add useMemo;

How to manage state without using Subject or imperative manipulation in a simple RxJS example?

I have been experimenting with RxJS for two weeks now, and although I love it in principle I just cannot seem to find and implement the correct pattern for managing state. All articles and questions appear to agree:
Subject should be avoided where possible in favor of just pushing state through via transformations;
.getValue() should be deprecated entirely; and
.do should perhaps be avoided except for DOM manipulation?
The problem with all such suggestions is that none of the literature appears to directly say what you should be using instead, besides "you'll learn the Rx way and stop using Subject".
But I cannot find a direct example anywhere that specifically indicates the correct way to perform both additions and removals to a single stream/object, as the consequence of multiple other stream inputs, in a stateless and functional manner.
Before I get pointed in the same directions again, problems with uncovered literature are:
The Introduction to Reactive Programming You've been missing: great starting text, but does not specifically address these questions.
The TODO example for RxJS comes with React and involves explicit manipulation of Subjects as proxies for React Stores.
http://blog.edanschwartz.com/2015/09/18/dead-simple-rxjs-todo-list/ : explicitly uses a state object for addition and removal of items.
My perhaps 10th rewrite of the standard TODO follows - My prior iterations covered include:
starting with a mutable 'items' array - bad as state is explicit and imperatively managed
using scan to concatenate new items to an addedItems$ stream, then branching another stream where the removed items were deleted - bad as the addedItems$ stream would grow indefinitely.
discovering BehaviorSubjectand using that - seemed bad since for each new updatedList$.next() emission, it requires the previous value to iterate, meaning that Subject.getValue() is essential.
trying to stream the result of the inputEnter$ addition events into filtered removal events - but then every new stream creates a new list, and then feeding that into the toggleItem$ and toggleAll$ streams means that each new stream is dependent on the previous, and so causing one of the 4 actions (add, remove, toggle item or toggle all) requires the whole chain to be unnecessarily run through again.
Now I have come full circle, where I am back to using both Subject (and just how is it supposed to be successively iterated upon in any way without using getValue()?) and do, as show below. Myself and my colleague agree this is the clearest way, yet it of course seems the least reactive and most imperative. Any clear suggestions on the correct way for this would be much appreciated!
import Rx from 'rxjs/Rx';
import h from 'virtual-dom/h';
import diff from 'virtual-dom/diff';
import patch from 'virtual-dom/patch';
const todoListContainer = document.querySelector('#todo-items-container');
const newTodoInput = document.querySelector('#new-todo');
const todoMain = document.querySelector('#main');
const todoFooter = document.querySelector('#footer');
const inputToggleAll = document.querySelector('#toggle-all');
const ENTER_KEY = 13;
// INTENTS
const inputEnter$ = Rx.Observable.fromEvent(newTodoInput, 'keyup')
.filter(event => event.keyCode === ENTER_KEY)
.map(event => event.target.value)
.filter(value => value.trim().length)
.map(value => {
return { label: value, completed: false };
});
const inputItemClick$ = Rx.Observable.fromEvent(todoListContainer, 'click');
const inputToggleAll$ = Rx.Observable.fromEvent(inputToggleAll, 'click')
.map(event => event.target.checked);
const inputToggleItem$ = inputItemClick$
.filter(event => event.target.classList.contains('toggle'))
.map((event) => {
return {
label: event.target.nextElementSibling.innerText.trim(),
completed: event.target.checked,
};
})
const inputDoubleClick$ = Rx.Observable.fromEvent(todoListContainer, 'dblclick')
.filter(event => event.target.tagName === 'LABEL')
.do((event) => {
event.target.parentElement.classList.toggle('editing');
})
.map(event => event.target.innerText.trim());
const inputClickDelete$ = inputItemClick$
.filter(event => event.target.classList.contains('destroy'))
.map((event) => {
return { label: event.target.previousElementSibling.innerText.trim(), completed: false };
});
const list$ = new Rx.BehaviorSubject([]);
// MODEL / OPERATIONS
const addItem$ = inputEnter$
.do((item) => {
inputToggleAll.checked = false;
list$.next(list$.getValue().concat(item));
});
const removeItem$ = inputClickDelete$
.do((removeItem) => {
list$.next(list$.getValue().filter(item => item.label !== removeItem.label));
});
const toggleAll$ = inputToggleAll$
.do((allComplete) => {
list$.next(toggleAllComplete(list$.getValue(), allComplete));
});
function toggleAllComplete(arr, allComplete) {
inputToggleAll.checked = allComplete;
return arr.map((item) =>
({ label: item.label, completed: allComplete }));
}
const toggleItem$ = inputToggleItem$
.do((toggleItem) => {
let allComplete = toggleItem.completed;
let noneComplete = !toggleItem.completed;
const list = list$.getValue().map(item => {
if (item.label === toggleItem.label) {
item.completed = toggleItem.completed;
}
if (allComplete && !item.completed) {
allComplete = false;
}
if (noneComplete && item.completed) {
noneComplete = false;
}
return item;
});
if (allComplete) {
list$.next(toggleAllComplete(list, true));
return;
}
if (noneComplete) {
list$.next(toggleAllComplete(list, false));
return;
}
list$.next(list);
});
// subscribe to all the events that cause the proxy list$ subject array to be updated
Rx.Observable.merge(addItem$, removeItem$, toggleAll$, toggleItem$).subscribe();
list$.subscribe((list) => {
// DOM side-effects based on list size
todoFooter.style.visibility = todoMain.style.visibility =
(list.length) ? 'visible' : 'hidden';
newTodoInput.value = '';
});
// RENDERING
const tree$ = list$
.map(newList => renderList(newList));
const patches$ = tree$
.bufferCount(2, 1)
.map(([oldTree, newTree]) => diff(oldTree, newTree));
const todoList$ = patches$.startWith(document.querySelector('#todo-list'))
.scan((rootNode, patches) => patch(rootNode, patches));
todoList$.subscribe();
function renderList(arr, allComplete) {
return h('ul#todo-list', arr.map(val =>
h('li', {
className: (val.completed) ? 'completed' : null,
}, [h('input', {
className: 'toggle',
type: 'checkbox',
checked: val.completed,
}), h('label', val.label),
h('button', { className: 'destroy' }),
])));
}
Edit
In relation to #user3743222 very helpful answer, I can see how representing state as an additional input can make a function pure and thus scan is the best way to represent a collection evolving over time, with a snapshot of its previous state up to that point as an additional function parameter.
However, this was already how I approached my second attempt, with addedItems$ being a scanned stream of inputs:
// this list will now grow infinitely, because nothing is ever removed from it at the same time as concatenation?
const listWithItemsAdded$ = inputEnter$
.startWith([])
.scan((list, addItem) => list.concat(addItem));
const listWithItemsAddedAndRemoved$ = inputClickDelete$.withLatestFrom(listWithItemsAdded$)
.scan((list, removeItem) => list.filter(item => item !== removeItem));
// Now I have to always work from the previous list, to get the incorporated amendments...
const listWithItemsAddedAndRemovedAndToggled$ = inputToggleItem$.withLatestFrom(listWithItemsAddedAndRemoved$)
.map((item, list) => {
if (item.checked === true) {
//etc
}
})
// ... and have the event triggering a bunch of previous inputs it may have nothing to do with.
// and so if I have 400 inputs it appears at this stage to still run all the previous functions every time -any- input
// changes, even if I just want to change one small part of state
const n$ = nminus1$.scan...
The obvious solution would be to just have items = [], and manipulate it directly, or const items = new BehaviorSubject([]) - but then the only way to iterate on it appears to be using getValue to expose the previous state, which Andre Stalz (CycleJS) has commented on in the RxJS issues as something that shouldn't really be exposed (but again, if not, then how is it usable?).
I guess I just had an idea that with streams, you weren't supposed to use Subjects or represent anything via a state 'meatball', and in the first answer I'm not sure how this doesn't introduce mass chained streams which are orphaned/grow infinitely/have to build on each other in exact sequence.
I think you already found a good example with : http://jsbin.com/redeko/edit?js,output.
You take issue with the fact that this implementation
explicitly uses a state object for addition and removal of items.
However, thas is exactly the good practice you are looking for. If you rename that state object viewModel for example, it might be more apparent to you.
So what is state?
There will be other definitions but I like to think of state as follows:
given f an impure function, i.e. output = f(input), such that you can have different outputs for the same input, the state associated to that function (when it exists) is the extra variable such that f(input) = output = g(input, state) holds and g is a pure function.
So if the function here is to match an object representing a user input, to an array of todo, and if I click add on a todo list with already have 2 todos, the output will be 3 todos. If I do the same (same input) on a todo list with only one todo, the output will be 2 todos. So same input, different outputs.
The state here that allows to transform that function into a pure function is the current value of the todo array. So my input becomes an add click, AND the current todo array, passed through a function g which give a new todo array with a new todo list. That function g is pure. So f is implemented in a stateless way by making its previously hidden state explicit in g.
And that fits well with functional programming which revolves around composing pure functions.
Rxjs operators
scan
So when it comes to state management, with RxJS or else, a good practice is to make state explicit to manipulate it.
If you turn the output = g(input, state) into a stream, you get On+1 = g(In+1, Sn) and that's exactly what the scan operator does.
expand
Another operator which generalizes scan is expand, but so far I had very little use of that operator. scan generally does the trick.
Sorry for the long and mathy answer. It took me a while to get around those concepts and that's the way I made them understandable for me. Hopefully it works for you too.

Categories

Resources