I just spent hours trying to understand what broke my sample app before I found this:
Working example:
let cards = this.props.cards.map(
(card) => {
return <Card id = {card.id}
title = {card.title}
description = {card.description}
color = {card.color}
tasks = {card.tasks} />
});
Broken example:
let cards = this.props.cards.map(
(card) => {
return
<Card id = {card.id}
title = {card.title}
description = {card.description}
color = {card.color}
tasks = {card.tasks} />
});
Should the JSX syntax be this sensitive or am I missing something else?
In your broken example result will be
[undefined, ...., undefined]
because JS interpreter understands it like this(note ; after return),
let cards = this.props.cards.map(
(card) => {
return;
<Card id = {card.id}
title = {card.title}
description = {card.description}
color = {card.color}
tasks = {card.tasks} />
});
if you add new line after return - JS interpreter inserts semicolon automatically, and this is not problem with JSX
Related
I'm making a blackjack hand simulation and I've encountered an issue with my code.
The game goes like this: users gets two random cards and a total of the points, clicks 'hit' to get another random card from the deck. Now that's all working but there's one more rule: if that card is an Ace, user chooses if they want to get 1 or 10 points. I implemented it before when I only had one card at a time with useEffect, however now I refactored my code and the total isn't kept in useState + the array has two cards that need to evaluated, not the most recent one.
I've tried putting my loop and if statement in a useEffect and conditionally render the Popup to let the user decide (with and without dependencies), but when I put the useState() to trigger the condition, it throws an error that there have been too many renders and I'm not sure why that is.
Here's my Home component:
import {useState, useEffect} from 'react'
import Card from '../components/Card';
import Total from '../components/Total';
import Popup from '../components/Popup'
import {shuffle} from '../hooks/shuffleCards'
import {deckArray} from '../utils/data'
export default function Home(){
const startHandSize = 2
const [starterDeck, setStarterDeck] = useState(shuffle(deckArray))
const [howManyDealt, setHowManyDealt] = useState(startHandSize)
const [triggerPopup, setButtonPopup] = useState(false)
const deal = () => {
setHowManyDealt(startHandSize)
setStarterDeck(shuffle(deckArray))
}
const hit = () => !bust && setHowManyDealt(prev => prev + 1)
const usersCards = starterDeck.slice(-howManyDealt)
var total = 0
usersCards.forEach(function (arrayItem) {
if(arrayItem.card === "A"){
alert("you have an ace")
}
else{
total += arrayItem.value
}
});
const bust = total > 21;
return(
<div>
<button onClick={deal}>DEAL</button>
<button disabled={bust} onClick={hit}>HIT</button>
<button disabled={bust}>STAND</button>
<Total total={total} usersCards={usersCards}/>
<Card usersCards={usersCards}/>
{triggerPopup && <Popup total={total} setButtonPopup={setButtonPopup}/>}
</div>
)
}
and my Popup:
export default function Popup({total, setButtonPopup}){
const handleClick = (points) => {
total += points
setButtonPopup(false)
}
return(
<div className="popup">
<div className="popup-inner">
<h4>You've got an Ace. Would you like to collect 1 or 10 points?</h4>
<button className=".btn-popup" onClick={() => handleClick(1)}>1 POINT</button>
<button className=".btn-popup" onClick={() => handleClick(10)}>10 POINTS</button>
</div>
</div>
)
}
Any help much appreciated!
Good attempt. However, there seems to be a general misunderstanding about state. Consider this code:
const handleClick = (points) => {
total += points
setButtonPopup(false)
}
total is a purely local variable to Popup, so this += pretty much does nothing. To change state in the caller, you'd normally pass a callback that can trigger a setState and move the new value for total into state.
Remember: any data change must happen immutably, and if you want to trigger a re-render, you have to set state. Of course, there are ways to circumvent this flow using refs and so forth, but these are escape hatches you shouldn't use if you don't have to.
However, a design with total kept in state strikes me as redundant. We already know the total based on the cards in play. A better strategy seems to be having ace values individually settable via the popup modal, assuming you don't want to auto-compute these ace values to be as high as possible without busting or use a toggle switch instead of a modal.
I kept going with my code from your previous question and added the modal. I'm treating high aces as 11 per the rules of Blackjack, but you can easily make that 10 if you want.
As before, I'm hoping you can apply the techniques here to your code. The keys are the handleAceSet callback and the new piece of state aceToSet, which is a ace the user has picked, or null if the user hasn't chosen an ace. aceToSet is like your setButtonPopup, but tracks an object or null rather than a boolean. When aceToSet isn't null, the user has selected an ace and we show the modal to let them pick a value for it.
handleAceSet may seem a bit complex, but it has to be due to immutability. It finds the index of the ace the user wants to set in the deck array, then creates a new object at this index with the new value and glues the subarray slices before and after the index back together.
// utility library "import"
const cards = (() => {
const shuffle = a => {
a = a.slice();
for (let i = a.length - 1; i > 0; i--) {
const j = ~~(Math.random() * (i + 1));
const x = a[i];
a[i] = a[j];
a[j] = x;
}
return a;
};
const frz = (...args) => Object.freeze(...args);
const suits = frz([..."HCSD"]);
const faces = frz([..."AJQK"]);
const pips = frz([...Array(9)].map((_, i) => i + 2));
const ranks = frz([...pips, ...faces]);
const cards = frz(
suits.flatMap(s =>
ranks.map(r =>
frz({
rank: r,
suit: s,
str: r + s,
value: isNaN(r) ? (r === "A" ? 1 : 10) : r,
})
)
)
);
const shuffled = () => shuffle(cards);
return {shuffled};
})();
const {Fragment, useState} = React;
const AceSetterModal = ({handleSetLow, handleSetHigh}) => (
<div>
<button onClick={handleSetLow}>Set ace low</button>
<button onClick={handleSetHigh}>Set ace high</button>
</div>
);
const Card = ({card, handleAceSet}) => (
<div>
{card.str}
{card.rank === "A" && (
<Fragment>
{" "}
<button onClick={handleAceSet}>
Set ({card.value})
</button>
</Fragment>
)}
</div>
);
const Game = () => {
const startHandSize = 2;
const goal = 21;
const lowAce = 1;
const highAce = 11;
const [deck, setDeck] = useState(cards.shuffled());
const [cardsDealt, setCardsDealt] = useState(startHandSize);
const [aceToSet, setAceToSet] = useState(null);
const handleAceSet = value => {
setDeck(deck => {
const i = deck.findIndex(e => e.str === aceToSet.str);
return [
...deck.slice(0, i),
{...aceToSet, value},
...deck.slice(i + 1),
];
});
setAceToSet(null);
};
const deal = () => {
setCardsDealt(startHandSize);
setDeck(cards.shuffled());
};
const hit = () => !bust && setCardsDealt(prev => prev + 1);
const cardsInPlay = deck.slice(-cardsDealt);
const total = cardsInPlay.reduce((a, e) => a + e.value, 0);
const bust = total > goal;
return (
<div>
{aceToSet ? (
<AceSetterModal
handleSetLow={() => handleAceSet(lowAce)}
handleSetHigh={() => handleAceSet(highAce)}
/>
) : (
<Fragment>
<button onClick={deal}>Deal</button>
<button disabled={bust} onClick={hit}>
Hit
</button>
<div>
{cardsInPlay.map(e => (
<Card
key={e.str}
handleAceSet={() => setAceToSet(e)}
card={e}
/>
))}
</div>
<div>Total: {total}</div>
<div>{bust && "Bust!"}</div>
</Fragment>
)}
</div>
);
};
ReactDOM.createRoot(document.querySelector("#app"))
.render(<Game />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="app"></div>
I am wanting to take a string and get the JSX to replace all the words in between brackets to be bold. I got it to work with this, but just wondering if there is a better way of going about this?
const jsxArray = [];
let unformattedString = "[name] Hi there, my name is [name], I am [age] years old, and I work in the field of [profession]";
const boldStrings = unformattedString.match(/(?<=\[).+?(?=\])/g);
const notBoldStrings = unformattedString.split(/\[.+?\]/g);
let j = 0;
if (boldStrings !== null) {
notBoldStrings.forEach((notBoldString, index) => {
if (index === 0 && notBoldStrings === "") {
// the first word should be bolded
jsxArray.push(<b>{boldStrings[j]}</b>);
} else {
jsxArray.push(notBoldString);
jsxArray.push(<b>{boldStrings[j]}</b>);
}
j++;
});
} else {
jsxArray.push(notBoldStrings[0]);
}
The expected output would be:
name Hi there, my name is name, I am age years old, and I work in the field of profession
You can use this code:
export default function App() {
let unformattedString =
"[name] Hi there, my name is [name], I am [age] years old, and I work in the field of [profession]";
const boldString = (str, substr) =>{
str.replaceAll(substr, `<b>${substr}</b>`);
}
const strArr = unformattedString.split(" ");
const formattedString = strArr
.map((item) => {
let strBold = item.match(/(?<=\[).+?(?=\])/g);
if (strBold) {
return boldString(item, strBold);
} else {
return item;
}
})
.join(" ");
return (
<div className="App">
<div dangerouslySetInnerHTML={{ __html: formattedString }} />
</div>
);
}
To see its result: codesandbox.io
But about dangerouslySetInnerHTML, Setting HTML from code is risky because it’s easy to inadvertently expose your users to a cross-site scripting (XSS) attack. And recommended using the html-react-parser package in your React project.
npm install html-react-parser
To use:
import parse from 'html-react-parser'
const yourHtmlString = '<h1>Hello</h1>'
and in return part:
<div>
{parse(yourHtmlString)}
</div>
I've created a grid component in React. I have an array of strings named 'availableColors' in which I am storing the css class names I want to use.
In the 'RandomColorGrid' component I'm setting the initial colors of each grid item in 'useState', assigning an index from 'availableColors' to each item.
Each item in the grid calls 'changeColors()' onClick. Inside that method I reassign the calue of each 'box' in 'colors' with a new randomly chosen index from 'availableColors'.
This works well enough but feels a little clunky. There are two things I am trying to improve but am getting stuck.
First; I would like to use each color only once when the 'changeColors()' function is called. Currently it's possible for the same color to be used on more than one grid item and I would like them to be four unique colours each time.
Second; I would like for no item to be the same color twice in a row. So for any given item I would line to exclude that item's current color from the possible random choices.
I've been trying to achieve this by taking the color index of the current color and forming a new array of colors to randomly select from for each item and then another array to try and track the colors that have already been used in order to avoid duplicates but in doing so have gotten into a real mess. This leads me to believe that my design is probably bad from the start.
How can I improve on this?
import React, { useState } from "react";
const availableColors = ["red", "green", "blue", "yellow"];
const changeColors = (colors, setColors) => {
colors.box1 = availableColors[randomNumber(colors.box1)];
colors.box2 = availableColors[randomNumber(colors.box2)];
colors.box3 = availableColors[randomNumber(colors.box3)];
colors.box4 = availableColors[randomNumber(colors.box4)];
setColors({ ...colors });
};
const randomNumber = (currentColour) => {
let indices = [0, 1, 2, 3];
indices.splice(availableColors.indexOf(currentColour), 1);
return indices[Math.floor(Math.random() * indices.length)];
};
export const RandomColorGrid = () => {
let [colors, setColors] = useState({
box1: availableColors[0],
box2: availableColors[1],
box3: availableColors[2],
box4: availableColors[3],
});
return (
<div className="grid">
<div
className={`${colors.box1}`}
onClick={() => changeColors(colors, setColors)}
/>
<div
className={`${colors.box2}`}
onClick={() => changeColors(colors, setColors)}
/>
<div
className={`${colors.box3}`}
onClick={() => changeColors(colors, setColors)}
/>
<div
className={`${colors.box4}`}
onClick={() => changeColors(colors, setColors)}
/>
</div>
);
};
Your problems come from not respecting the immutability of objects.
You change an object and rely on the object not changing in the next line (in changeColors)
The solution would be to copy new arrays of the available colors, and using .filter to make sure we dont repeat the same colors twice by replacing the new currentlyAvailableColors array to only include colors that are ok to use
const changeColors = (colors, setCurrentColours) => {
const nextColors = {};
let currentlyAvailableColors = [...availableColors];
nextColors.box1 = getRandomOption(colors.box1, currentlyAvailableColors)
currentlyAvailableColors = currentlyAvailableColors.filter(col => col !== nextColors.box1);
nextColors.box2 = getRandomOption(colors.box2, currentlyAvailableColors)
currentlyAvailableColors = currentlyAvailableColors.filter(col => col !== nextColors.box2);
nextColors.box3 = getRandomOption(colors.box3, currentlyAvailableColors)
currentlyAvailableColors = currentlyAvailableColors.filter(col => col !== nextColors.box3);
nextColors.box4 = getRandomOption(colors.box4, currentlyAvailableColors)
currentlyAvailableColors = currentlyAvailableColors.filter(col => col !== nextColors.box4);
setCurrentColours({ ...nextColors });
};
Heres a working codepen
https://codepen.io/yftachman/pen/XWZMqVZ
I implement WYSIWYG editor with draftjs and I have a set of rules for typography fixing. I use Modifier.replaceText to replace what I want, but the problem is, when I call it, it removes inlineStyles in replaced text.
Here is a block of code that I use for typography. Inputs are rules (array with rules) and editorState.
rules.forEach(({ toReplace, replace }) => {
const blockToReplace = [];
let contentState = editorState.getCurrentContent();
const blockMap = contentState.getBlockMap();
blockMap.forEach(contentBlock => {
const text = contentBlock.getText();
let matchArr;
while ((matchArr = toReplace.exec(text)) !== null) {
const start = matchArr.index;
const end = start + matchArr[0].length;
const blockKey = contentBlock.getKey();
const blockSelection = SelectionState.createEmpty(blockKey).merge({
anchorOffset: start,
focusOffset: end,
});
blockToReplace.push(blockSelection);
}
});
blockToReplace.reverse().forEach((selectionState) => {
contentState = Modifier.replaceText(
contentState,
selectionState,
text.replace(search, replace)
);
});
editorState = EditorState.push(editorState, contentState);
});
So, my input is: *bold...*
The wrong output is: *bold*…
Should be: *bold…*
Note: asterisks are for bold designation, change is three dots to horizontal ellipsis (U+2026)
Anybody any idea? I google it for two days and nothing...
I have written a small piece of code inside the return. for example
const Demo = (props) => {
return (
<div>
props.map((val, index) => (
<h2>{val.fileName}</h2>
))
</div>
)
}
The output is coming like this:
F:\test\form\student.html
But inside I don't want this type of output. I want to modify the output like: F:\test\form\form.pdf
The last student.html will remove and the form must become 2 times will repeat and lastly, the extension is pdf
original output: F:\test\form\student.html
desired output: F:\test\form\form.pdf
can you help how to solve this problem?
Hope this will help as per my understanding of your question
const Demo = (props) => {
const processedFileNames = props.map((val, index) = {
const splitFileName = val.fileName.split('\\');
splitFileName[splitFileName.length - 1] = splitFileName[splitFileName.length - 2];
return splitFileName.join('\\') + '.pdf';
})
return (
<div>
processedFileNames.map((val, index) => (
<h2>{val}</h2>
))
</div>
)
}
Please let me know if you need any other help.