React hooks boolean in state not properly being set - reactjs

I apologize but I could not find an answer that helps with my problem.
I'm making a pokemon 20 questions game and recently added a new question to ask.
I'll do my best to keep it clear. But every answer is stored in state
Like this:
const [type, setType] = useState(null);
const [weakness, setWeakness] = useState(null);
const [evolve, setEvolve] = useState(null);
etc.
So I have buttons set up that assign these values as a user progresses through the game.
But my evolve question breaks the game.
Here is the function
const evolveAssign = (canEvolveBoolean) => {
setEvolve(canEvolveBoolean);
setPokemonData((prevPokeArray) => // pokemon data is a giant array of pokemon objects
prevPokeArray.filter((pokemon) => {
if (canEvolveBoolean === true) {
return pokemon.prev_evolution || pokemon.next_evolution; //checks if the properties exist. Pokemon that do not evolve, do not have such properties.
} else {
return !pokemon.prev_evolution && !pokemon.next_evolution;
}
})
)};
This is how it is being returned. Which I admit, I'm not sure if this is the best way to display questions.
{type && weakness && !evolve && (
<div>
<strong>Can your Pokemon Evolve?</strong>
<EvolveBtn mapEvolutions={mapEvolutions} onClick={evolveAssign} />
</div>
)}
The problem
When the user hits 'No' it will pass false to the function, and it does seem to work! But the question gets stuck only on "No" So I logged
<p>evolve = {evolve}</p>
and it will show 'evolve = '. So somehow, evolve is not being set at all? I haven't had this issue before with the other questions.
Is there something weird going on because it's a Boolean? Is my JS logic bad? I'm pretty much out of ideas. I apologize if this was unclear/messy. I'd love any feedback as I'm still learning.
Thank you very much.
EDIT: Created a Codesandbox here!
https://codesandbox.io/s/damp-bush-u6yel?file=/src/App.js
The error can be seen if you choose Bug -> Fire -> "No". It will break after that.

I took a look at the sandbox. Several things can be improved but I will just focus on the evolve issue.
In JavaScript null and false are falsy values. !null or !false will return true. This is why the questions are rendered incorrectly after selecting evolve.
A quick and easy fix would be to check if evolve is null or the opposite if the user already selected evolve.
{
type && weakness && (evolve === null) && (
<div style={{ fontSize: 50 }}>
<strong>Can your Pokemon Evolve?</strong>
<EvolveBtn mapEvolutions={mapEvolutions} onClick={evolveAssign} />
</div>
)
}
{/* What height? */ }
{
type && weakness && (evolve !== null) && !height && (
<div style={{ fontSize: 50 }}>
<strong>Select Height</strong>
<HeightBtn mapHeight={mapHeight} onClick={heightAssign} />
</div>
)
}
Similar checks need to be used in the rest of the conditions if you simply want to make the app work.

Related

What is the correct way to get the "final" input value of an AutoComplete component in React?

The bounty expires in 7 days. Answers to this question are eligible for a +50 reputation bounty.
JasperMW wants to draw more attention to this question:
I've really been stuck at this problem for a few days already, and my deadline is approaching. Please help!
I've been getting "Cannot update during state transition" errors on my AutoComplete component, and after some searching around I found that it was because of my renderInput code.
I try to read the input during renderInput, but I can't edit the state then, so I can't read the input when I need to. It's all very confusing to me.
Is there a way to only execute a method when a value is really selected? EG when Enter is pressed when highlighting something, when an option in the popup is clicked, etc.
Below is the renderInput code:
public render() {
const { classes } = this.props;
return (
<div className={classes.autoSuggest}>
<Autocomplete
ListboxProps={{ style: { maxHeight: 200, overflow: 'auto' } }}
autoHighlight={true}
disablePortal={false}
options={this.getOptions()}
onClick={(_event, value) => this.onSelect(value)}
renderInput={(input) => { return this.onInput(input);}}
/>
</div>
);
}
private onInput(input: AutocompleteRenderInputParams) {
if (!this.state.alleenUnCodes &&
input.inputProps.value !== undefined &&
input.inputProps.value.toString() !== "" &&
input.inputProps.value.toString().charAt(0) === '/') {
this.setState({alleenUnCodes: true});
}
if (this.state.alleenUnCodes &&
input.inputProps.value !== undefined &&
input.inputProps.value.toString().charAt(0) !== '/') {
this.setState({alleenUnCodes: false});
}
return <TextField {...input} label={'Type GEVI/UN of /UN code (bv 20/1002, of /20)'}/>;
}
EDIT: I found the answer... I tried to read the input to filter my options. However, apparently I can just use the filterOptions param!

Multiple map functions and returning HTML

I'm running into issues with multiple map functions and displaying data. I believe I'm missing some returns in the appropriate spots, but can't find a good example online. Any help is appreciated. Thanks!
Object.keys(patternData).map((pattern) => {
if (patternData[pattern]['Matches'] && Object.keys(patternData[pattern] ['Matches']).length !== 0) {
Object.keys(patternData[pattern]['Matches']).map((matchSymbol) =>
<p> {matchSymbol} </p>
)
}
})

How would I re-render the list everytime the state changes for this text filter using react hooks

I have updated this question with clearer and more concise code on 15/03/22.
I have a text filter and it filters an array (displaying a list) correctly if it matches something in the displayed list, but as the user deletes character by character the filter does not change. I need it to keep matching as the user delete's chars.
Here is the filter which is set as the onChange for the text field:
const [searchInputTitle, setSearchInputTitle] = useState<string>('');
const [filteredItemsArrayState, setFilteredItemsArrayState] = useState<IListItems[]>(props.requests);
const searchTitles = () => {
let filteredArrayByTitle: IListItems[] = [];
filteredArrayByTitle = theRequestsState.filter((item) => {
return item.Title && item.Title.toLowerCase().indexOf(searchInputTitle.toLowerCase()) >= 0;
});
console.log(searchInputTitle, 'searchInputTitle');
if (searchInputTitle && searchInputTitle.length > 0) {
setTheRequestsState(filteredArrayByTitle);
setIsFiltered(true);
} else if (searchInputTitle && searchInputTitle.length === 0) {
const AllItems = props.requests;
let sortedByID: IListItems[] = AllItems.sort((a, b) => a.Id > b.Id ? 1 : -1);
setTheRequestsState(sortedByID);
setIsFiltered(false);
}
};
<TextField
onChange={searchTitles}
value={searchInputTitle}
/>
useEffect(() => {
_onTitleFilterChange(null, searchInputTitle);
if (isFiltered == true) {
setFunctionalArea(null, null);
setRequestingFunction(null, null);
}
}, [isFiltered, searchInputTitle]);
////////
<DetailsList className={styles.DetailsList}
items={filteredItemsArrayState.slice((ListPage - 1) * 50, ((ListPage * 50)))}
/>
Can anyone see why the render is not updating on deletion of char and what I could use to do so?
Update: As I type a character into the search I can see it's finding the searched for char/word and also if I delete chars now it searches and finds actively, but as soon as I stop typing it reverts back to the original array of items.
Can you try to filter the array you give to the DetailsList so you never lost the data ..?
filteredItemsArrayState.filter(s => {
if (searchInputTitle.length === 0)
return true
else
return s.Title.toLowerCase().match(searchInputTitle.toLowerCase())
}).map(....)
Found the reason. Thanks for all your help.
The issue was the setIsFiltered(true); in the title filter function. Other filters were running based on this line and these other filters were recalling the unfiltered list everytime a key was pressed. I removed this line and the issue was fixed.
I have come to realise that useEffect is almost completely mandatory on most of my projects and is React hooks are quite a departure from the original React syntax.

Flickity cell selector in React

Basically I have a dropdown in which each option has an attribute corresponding to the ID of an image. My goal is to go to that image when an option is chosen. For that I am trying to use:
const myCustomNext = () => {
flkty.selectCell(somevar)
};
somevar is initially set to #someid, when I click my button, it goes to the cell with that ID perfectly.
The issue starts once I update the value of somevar. As soon as I do and then click the button, I get the following Error:
"Cannot read property 'selectCell' of null"
I logged both the initital somevar and the updated one. Other than the ID itself, they are absolutely identical so I have no clue where I am going wrong. I tried switchen the static and reloadOnUpdate settings but that didn't help.
Here a more complete example that might show better what I am trying to do:
const FlickTest = () => {
const [varimg, setVarimg] = useState("#cG9zdDoxNA");
let flkty = null;
function setVariant(e) {
let index = e.target.selectedIndex;
let optionElement = e.target.childNodes[index]
let option = optionElement.getAttribute('data-imgid');
setVarimg(`#${option}`);
}
const myCustomNext = () => {
flkty.selectCell(varimg)
};
return (
<>
<button onClick={myCustomNext}>My custom next button</button>
<select onChange={setVariant}>
{variants.map((variant) =>
<option data-imgid={variant.gallerie[0].id} value={variant.farbe} key={variant.farbe}>{variant.farbe}</option>
)}
</select>
<Flickity
flickityRef={c => (flkty = c)}
className={'carousel'}
elementType={'div'}
options={flickityOptions}
disableImagesLoaded={true}
reloadOnUpdate={true}
static={true}
>
{variants.map((galitem) =>
galitem.gallerie.map((galimg) =>
<div key={galimg.id} id={galimg.id.replace(/[^\w\s]/gi, '')}>
<span>{galimg.id}</span>
<Image fluid={galimg.localFile.childImageSharp.fluid} />
</div>
)
)}
</Flickity>
</>
)
}
Any ideas or pointers would be much appreciated :)
Switched from a dropdown to buttons just to simplify the whole thing and see where it goes wrong. Seems like flickity only accepts the value directly but not from state or any other variable.
This works:
const selectSlide = (e) => {
flkty.selectCell( `.${e.target.getAttribute("data-selector")}` )
};
...
<button onClick={selectSlide} data-selector={variant.gallerie[0].id} key={variant.farbe}>{variant.farbe}</button>
If anybody knows if this is a flickity thing or (more likely) I was doing something completely wrong I'd still appreciate some pointers so I know better next time :)

How to calculate and store item property when rendering that item?

I need to calculate and store values from items, what been filtered earlier by some criteria and rendered:
<ItemsList>
items ?
items.map(
item =>
someCriteria &&
<ItemComponent details={item}>
)
<ItemsList />
I need something like let someVar += item.value somewhere to use it after map ends and before criteria changes.
I cant store it on-fly in local state, because of re-rendering while map runs
I see the only way to do this, for now, is to store it in localStorage, but it is even more stupid, I think.
Because of app architecture there is no redux store, only one state in main file.
Thanks
Though I don't quite understand what exactly you want to create. Here is something I think might be useful for you
var someVar = '';
const renderable =
<ItemsList>
{ items ?
items.map( item => {
if(someCriteria){
someVar += item.value;
return <ItemComponent details={item} key={a-must-key}>;
}
}
)
:
null
}
</ItemsList>
after the map end you can extract the value from the someVar

Resources