Reactjs: Pressing button to randomize an array - javascript

Basically, I am trying to make a card program that would pick five cards out of 52 in random. These cards must not repeat. I have already figured out the randomizer through traditional javascript. However, I am using ReactJs to make a button which if pressed, would create a new set of five cards.
class Reset extends React.Component {
constructor(props) {
super(props);
this.state = {...};
}
handleClick() {...}
render() {
return <button onClick={this.handleClick}>{...}</button>;
}
}
const cards = [
"A♥",
"A♠",
"A♦",
"A♣",
"2♣",
"3♣",
"4♣",
"5♣",
"6♣",
"7♣",
"8♣",
"9♣",
"10♣",
"K♣",
"Q♣",
"J♣",
"2♦",
"3♦",
"4♦",
"5♦",
"6♦",
"7♦",
"8♦",
"9♦",
"10♦",
"K♦",
"Q♦",
"J♦",
"2♥",
"3♥",
"4♥",
"5♥",
"6♥",
"7♥",
"8♥",
"9♥",
"10♥",
"K♥",
"Q♥",
"J♥",
"2♠",
"3♠",
"4♠",
"5♠",
"6♠",
"7♠",
"8♠",
"9♠",
"10♠",
"K♠",
"Q♠",
"J♠"
];
var hand = [];
function in_array(array, el) {
for (var i = 0; i < array.length; i++) if (array[i] == el) return true;
return false;
}
function get_rand(array) {
var rand = array[Math.floor(Math.random() * array.length)];
if (!in_array(hand, rand)) {
hand.push(rand);
return rand;
}
return get_rand(array);
}
for (var i = 0; i < 5; i++) {
document.write(get_rand(cards));
}
ReactDOM.render(<Reset />, document.getElementById("root"));
Basically, what would I have to fill in the parts with "..." in order for the code to rerandomize the pack.

Try something like this, I'm preserving alot of the code you already wrote. You really just have to move that logic into the handler.
Here's the sandbox as well: https://codesandbox.io/s/yv93w19pkz
import React from "react";
import ReactDOM from "react-dom";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
cards: ["A♥", "A♠", "A♦", "A♣", "2♣", "3♣", "4♣", "5♣", "6♣", "7♣", "8♣", "9♣", "10♣", "K♣", "Q♣", "J♣", "2♦", "3♦", "4♦", "5♦", "6♦", "7♦", "8♦", "9♦", "10♦", "K♦", "Q♦", "J♦", "2♥", "3♥", "4♥", "5♥", "6♥", "7♥", "8♥", "9♥", "10♥", "K♥", "Q♥", "J♥", "2♠", "3♠", "4♠", "5♠", "6♠", "7♠", "8♠", "9♠", "10♠", "K♠", "Q♠", "J♠"],
hand: []
}
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
const cards = this.state.cards
const newHand = []
function get_rand(array) {
var rand = array[Math.floor(Math.random() * array.length)];
if (!newHand.includes(rand)) {
newHand.push(rand);
} else {
get_rand(cards);
}
}
for (var i = 0; i < 5; i++) {
get_rand(cards);
}
this.setState({
hand: newHand
})
}
render() {
const { hand } = this.state
return (
<div>
{ hand ? (hand.map((card) => {
return <p>{card}</p>
})) : (
null
)}
<button onClick={this.handleClick}>Randomize
</button>
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);

Try to declare the cards in the state and update when you click on it

Related

ReactJS - Buttons not Re-Rendering on State Change?

React Newbie here,
import React, { Component } from "react";
class AudioList extends Component {
constructor(props) {
super(props);
this.audios = [];
this.buttonText = [];
for (let i = 0; i < this.props.songs.length; i++) {
this.audios.push(new Audio(this.props.songs[i].song_url));
this.buttonText.push(String(i));
}
this.state = {
songs: "",
buttonText: this.buttonText
};
}
componentWillMount() {
const songs = [];
for (let i = 0; i < this.props.songs.length; i++) {
this.audios[i].addEventListener("play", () => {
let stateArray = [...this.state.buttonText];
let stateArrayElement = { ...stateArray[i] };
stateArrayElement = "playing";
stateArray[i] = stateArrayElement;
console.log(stateArray);
this.setState({ buttonText: stateArray });
console.log(this.state.buttonText[i]);
});
songs.push(
<div className="song-preview">
<button
className="preview"
onClick={() => this.toggle(this.audios[i])}
>
{this.state.buttonText[i]}
</button>
</div>
);
}
this.setState({
songs: songs
});
}
componentWillUnmount() {
for (let i = 0; i < this.props.songs.length; i++) {
this.audios[i].pause();
}
}
getCurrentAudio() {
return this.audios.find(audio => false === audio.paused);
}
toggle(nextAudio) {
const currentAudio = this.getCurrentAudio();
if (currentAudio && currentAudio !== nextAudio) {
currentAudio.pause();
nextAudio.play();
}
nextAudio.paused ? nextAudio.play() : nextAudio.pause();
}
render() {
if (this.state.songs) {
return <div className="song-list">{this.state.songs}</div>;
} else {
return <div className="song-list"></div>;
}
}
}
export default AudioList;
I am using this code from a previous solution that I found on Stackoverflow (https://stackoverflow.com/a/50595639). I was able to implement this solution to solve my own challenge of needing to have multiple audio sources with one audio player and multiple buttons. However, I am now faced with a new challenge - I want a specific button's text to change when an event is fired up.
I came up with this implementation where the button text is based on an array in the state called buttonText. The buttons are rendered correctly on startup, but when the event listener picks up the event and changes the state, the text in the button is not re-rendering or changing, even though it is based on an element in an array in the state that is changing.
Does anyone have any suggestions about why it may be failing to re-render?
EDIT: Changing an individual array element in the state is based on React: how to update state.item[1] in state using setState?
I have restructured your code a bit (but it's untested it's tested now):
const songs = [
{
title: "small airplane long flyby - Mike_Koenig",
song_url: "http://soundbible.com/mp3/small_airplane_long_flyby-Mike_Koenig-806755389.mp3"
},
{
title: "Female Counts To Ten",
song_url: "http://soundbible.com/mp3/Female%20Counts%20To%20Ten-SoundBible.com-1947090250.mp3"
},
];
class AudioList extends React.Component {
audios = new Map();
state = {
audio: null,
song: null
};
componentDidUpdate() {
// stop playing if the song has been removed from props.songs
if (this.state.song && !this.props.songs.some(song => song.song_url === this.state.song.song_url)) {
this.toggle(this.state.audio, this.state.song);
}
}
componentWillUnmount() {
if (this.state.audio) {
this.state.audio.pause();
}
this.audios.clear();
}
toggle(audio, song) {
this.setState(state => {
if (audio !== state.audio) {
if (state.audio) {
state.audio.pause();
}
audio.play();
return { audio, song };
}
audio.pause();
return { audio: null, song: null };
});
}
getAudio(song) {
let audio = this.audios.get(song.song_url);
if (!audio) {
this.audios.set(song.song_url, audio = new Audio(song.song_url));
}
return audio;
}
render() {
return <div className="song-list">{
this.props.songs.map((song, i) => {
const audio = this.getAudio(song);
const playing = audio === this.state.audio;
return <div className="song-preview">
<button
className="preview"
onClick={this.toggle.bind(this, audio, song)}
>
{playing ? "playing" : (song.title || i)}
</button>
</div>
})
}</div>;
}
}
ReactDOM.render(<AudioList songs={songs} />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Edit: added a title to the song-objects and display them on the buttons
I simply moved all of the code from componentWillMount() to render(). I also removed 'songs' as a state variable and set it to a variable that exists only in render as songs is simply just a set of divs.
import React, { Component } from "react";
const audio1 =
"http://soundbible.com/mp3/small_airplane_long_flyby-Mike_Koenig-806755389.mp3";
const audio2 =
"http://soundbible.com/mp3/Female%20Counts%20To%20Ten-SoundBible.com-1947090250.mp3";
class AudioList extends Component {
constructor(props) {
super(props);
this.audios = [];
this.buttonText = [];
for (let i = 0; i < this.props.songs.length; i++) {
this.audios.push(new Audio(this.props.songs[i]));
this.buttonText.push(String(i));
}
this.state = {
buttonText: this.buttonText
};
}
componentWillUnmount() {
for (let i = 0; i < this.props.songs.length; i++) {
this.audios[i].pause();
}
}
getCurrentAudio() {
return this.audios.find(audio => false === audio.paused);
}
toggle(nextAudio) {
const currentAudio = this.getCurrentAudio();
if (currentAudio && currentAudio !== nextAudio) {
currentAudio.pause();
nextAudio.play();
}
nextAudio.paused ? nextAudio.play() : nextAudio.pause();
}
render() {
const songs = [];
for (let i = 0; i < this.props.songs.length; i++) {
this.audios[i].addEventListener("play", () => {
console.log("playing");
let stateArray = [...this.state.buttonText];
let stateArrayElement = { ...stateArray[i] };
stateArrayElement = "playing";
stateArray[i] = stateArrayElement;
console.log(stateArray);
this.setState({ buttonText: stateArray });
console.log(this.state.buttonText);
});
songs.push(
<div className="song-preview">
<button
className="preview"
onClick={() => this.toggle(this.audios[i])}
>
{this.state.buttonText[i]}
</button>
</div>
);
}
return (
<div>{songs}</div>
)
}
}
export default () => <AudioList songs={[audio1, audio2]} />;
The code now runs as expected.

How to update one child component alone in React Js

I was making react version of game Bingo. I added 25 buttons which are the child components.
Initially i have empty values in each button. Then on each click, i was trying to update the clicked button's values from 1 to 25.
But when i click on one button, to update the button label, all button's values are getting updated. Can anyone suggest the reason behind that?
App.js
import React from "react";
import "./styles.css";
import GameContainerOne from "./GameContainerOne";
export default function App() {
return (
<div className="App">
<GameContainerOne />
</div>
);
}
GameContainerOne.js
import React from "react";
import ButtonBox from "./ButtonBox";
class GameContainerOne extends React.Component {
constructor(props) {
super(props);
this.state = {
btnLabel: 0
};
}
handleClicked = () => {
if (this.state.btnLabel < 25) {
this.setState({ btnLabel: ++this.state.btnLabel });
console.log("after", this.state.btnLabel);
}
};
render() {
let menuItems = [];
let key = 0;
for (let i = 0; i < 5; i++) {
for (let j = 0; j < 5; j++) {
let index = "" + i + j;
// console.log(index)
key++;
menuItems.push(
<ButtonBox
key={key}
index={index}
value={this.state.btnLabel}
handleClicked={this.handleClicked.bind(this)}
/>
);
}
}
return <div className="wrapper">{menuItems}</div>;
// this.handleButtonBox()
}
}
export default GameContainerOne;
ButtonBox.js
import React from "react";
import Button from "#material-ui/core/Button";
class ButtonBox extends React.Component {
constructor(props) {
super(props);
this.state = {
initialBtnColor: "Default",
selectedBtnColor: "Primary"
};
}
handleClick = () => {
// console.log("before",this.state.btnLabel)
this.setState({ initialBtnColor: "Primary" });
return this.props.handleClicked;
};
render() {
console.log("Key=", this.props);
// const { index } = this.props.index;
console.log("Key=", this.props.index);
return (
<div>
<Button
variant="contained"
color={this.state.initialBtnColor}
onClick={this.props.handleClicked}
>
{this.props.value}
</Button>
</div>
);
}
}
export default ButtonBox;
Please find the codesandbox link : https://codesandbox.io/s/bingo-game-glk8v
Move the btnLabel state into the ButtonBox.
Example (using hooks):
// ButtonBox.js
import React from "react";
import Button from "#material-ui/core/Button";
function ButtonBox(props) {
const [buttonColor, setButtonColor] = React.useState("Default");
const [value, setValue] = React.useState(props.startValue);
return (
<div>
<Button
variant="contained"
color={buttonColor}
onClick={() => {
setValue(value + 1);
setButtonColor("Primary");
props.handleClicked(props.index);
}}
>
{value}
</Button>
</div>
);
}
export default ButtonBox;
// GameContainerOne.js
import React from "react";
import ButtonBox from "./ButtonBox";
function GameContainerOne(props) {
const handleClicked = React.useCallback(btnIndex => {
// Called after a button is clicked
}, []);
let menuItems = [];
let key = 0;
for (let i = 0; i < 5; i++) {
for (let j = 0; j < 5; j++) {
let index = "" + i + j;
key++;
menuItems.push(
<ButtonBox
key={key}
index={index}
startValue={0}
handleClicked={handleClicked}
/>
);
}
}
return <div className="wrapper">{menuItems}</div>;
}
export default GameContainerOne;
You change the state on click which is the source of labels for all the buttons.
You could have a separate state for all the buttons to avoid that and changed them based on the index on the button clicked.

Render function inside component and display in container on button click

I am newbie in react and javascript and recently start developed an app. I want to render the renderBoard array inside the boardContainer div after i click button . Thx for any answers. This is the code for my component:
class Main extends Component {
constructor(props) {
this.state = {
size: [2, 2],
renderBoard: false,
}
this.renderBoard = this.renderBoard.bind(this);
}
renderBoard() {
var newWorld = [];
var cellRow = [];
for(var i = 0; i < this.state.size[0]; i++) {
for (var j = 0; j < this.state.size[1]; j++){
cellRow.push(<Cell key={[i, j]} />);
}
newWorld.push(<div className="row" key={i}>{cellRow}</div>);
cellRow = [];
}
return newWorld;
}
render() {
return (
<div>
<div className="headerButtons">
<button className="submit" onClick= {this.renderBoard()}>Start</button>
</div>
Generation:
<div className="boardContainer">
{this.renderBoard()}
</div>
</div>
);
}
}
class Cell extends Component {
render() {
return (
<div className="cellContainer"></div>
);
}
}
export default Main;
You need to set the state when you click the button instead of calling your render function. Do something like this:
class Main extends Component {
constructor(props) {
super(props);
this.state = {
size: [2, 2],
renderBoard: false,
}
this.renderBoard = this.renderBoard.bind(this);
}
renderBoard() {
....
}
showBoard => () => {
this.setState({ renderBoard: true });
}
render() {
return (
<div>
<div className="headerButtons">
<button className="submit" onClick={this.showBoard}>Start</button>
</div>
Generation:
<div className="boardContainer">
{this.state.renderBoard ? this.renderBoard() : null}
</div>
</div>
);
}
}
or you could change your renderBoard function instead of using a ternary in your JSX like this:
renderBoard = () => {
if (this.state.renderBoard) {
....
} else {
return null;
}
}
I would also suggest renaming your state variable to be clearer. So maybe instead of renderBoard it would be showBoard. Then you'd use it like if (this.state.showBoard). But that isn't required to make it work.

Code Randomly Changing a State Element to 1

Working on a project that will allow you to search the Spotify database for songs, add it to a playlist, then add the playlist to your account, I run into an error when attempting to add a song to a playlist. Upon searching through the code to find and reason, and adding console logs to see where it goes wrong. It seems that in the render statement, the state element playlistTracks is changed from an Array with the added song inside it as an object to just the integer 1. This image shows the progression of console logs:
This is the code from App.js:
import React from 'react';
import './App.css';
import SearchBar from '../SearchBar/SearchBar';
import SearchResults from '../SearchResults/SearchResults';
import Playlist from '../Playlist/Playlist';
import Spotify from '../../util/Spotify.js';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
searchResults: [],
playlistName: 'New Playlist',
playlistTracks: []
}
this.addTrack = this.addTrack.bind(this);
this.removeTrack = this.removeTrack.bind(this);
this.updatePlaylistName = this.updatePlaylistName.bind(this);
this.savePlaylist = this.savePlaylist.bind(this);
this.search = this.search.bind(this);
}
addTrack(track) {
console.log(track);
if(this.state.playlistTracks.length !== 0) {
const ids = Playlist.collectIds(this.state.playlistTracks);
let newId = true;
for(let i = 0; i < ids.length; i++) {
if(ids[i] === track.id) {
newId = false;
}
}
if(newId) {
this.setState({playlistTracks: this.state.playlistTracks.push(track)});
}
} else {
this.setState({playlistTracks: this.state.playlistTracks.push(track)});
}
console.log(this.state.playlistTracks);
}
removeTrack(track) {
const ids = Playlist.collectIds(this.state.playlistTracks);
let trackIndex = -1;
for(let i = 0; i < ids.length; i++) {
if (ids[i] === track.id) {
trackIndex = i;
}
}
if (trackIndex !== -1) {
const newPlaylist = this.state.playlistTracks.splice(trackIndex, 1);
this.setState({playlistTracks: newPlaylist});
}
}
updatePlaylistName(name) {
this.setState({playlistName: name});
}
savePlaylist() {
let trackURIs = [];
for(let i = 0; i < this.state.playlistTracks.length; i++) {
trackURIs.push(this.state.playlistTracks[i].uri);
}
Spotify.savePlaylist(this.state.playlistName, trackURIs);
this.setState({playlistName: 'New Playlist', playlistTracks: []});
}
async search(term) {
const results = await Spotify.search(term);
this.setState({searchResults: results});
}
render() {
return (
<div id="root">
<h1>Ja<span className="highlight">mmm</span>ing</h1>
<div className="App">
<SearchBar onSearch={this.search} />
<div className="App-playlist">
{console.log(this.state.playlistTracks)}
<SearchResults searchResults={this.state.searchResults} onAdd={this.addTrack} />
<Playlist
playlistName={this.state.playlistName}
playlistTracks={this.state.playlistTracks}
onRemove={this.removeTrack}
onNameChange={this.updatePlaylistName}
onSave={this.savePlaylist}
/>
</div>
</div>
</div>
);
}
}
export default App;
The error is likely a result of pushing and mutating state in one line.
Try:
// Add Track.
addTrack = (track) => this.setState({playlistTracks: [...this.state.playlistTracks, track]})
Array.prototype.push returns the new number of elements in the array. It does not return the array itself. You are setting the state to the returned value, which is 1.
Here are some examples to illustrate what's happening:
What you expect to happen:
let someArray = [];
someArray.push('something');
console.log(someArray); // -> ['something']
But what you are actually doing is akin to:
let someArray = [];
someArray = someArray.push('something');
console.log(someArray); // -> 1

Call a callback each time state changes in React [duplicate]

This question already has answers here:
Why is setState in reactjs Async instead of Sync?
(8 answers)
Closed 5 years ago.
I have an app like:
Main.js-
import React, { Component } from 'react';
import _ from 'underscore';
import { pick_attributes } from '../utils/general';
import ApplicationsButtons from '../components/ApplicationsButtons';
import Roles from '../components/Roles';
let applications_url = 'http://127.0.0.1:8889/api/applications'
export default class Main extends Component {
constructor(props) {
super(props);
this.state = {
applications: [],
selected_app_id: 1,
roles: []
};
this.updateSelectedApp = this.updateSelectedApp.bind(this);
this.updateApplicationData = this.updateApplicationData.bind(this);
this.loadAppData = this.loadAppData.bind(this);
this.getSelectedApplicationData = this.getSelectedApplicationData.bind(this);
this.setRoles = this.setRoles.bind(this);
}
componentDidMount() {
this.loadAppData();
}
// componentDidUpdate() {
// this.updateApplicationData();
// }
updateApplicationData() {
this.setRoles();
}
loadAppData() {
let self = this;
$.ajax({
url: applications_url,
method: 'GET',
success: function(data) {
let objects = data.objects;
self.setState({applications_data: objects});
let apps_data = pick_attributes(objects, 'name', 'id');
self.setState({applications: apps_data});
self.updateApplicationData();
}
});
}
getSelectedApplicationData() {
let selected_app_id = this.state.selected_app_id;
let objects = this.state.applications_data;
for (let i = 0; i < objects.length; i++) {
let object = objects[i];
if (object.id == selected_app_id) {
return object
}
}
}
setRoles() {
let selected_app_id = this.state.selected_app_id;
let selected_app_object = this.getSelectedApplicationData();
let roles_data = selected_app_object.role_list;
let roles = pick_attributes(roles_data, 'name', 'id');
this.setState({roles});
}
updateSelectedApp(id) {
this.setState({selected_app_id: id});
}
render() {
return (
<div>
{this.state.selected_app_id}
<ApplicationsButtons
apps={this.state.applications}
clickHandler={this.updateSelectedApp}/>
<Roles roles={this.state.roles} />
</div>
);
}
}
ApplicationsButtons.js-
import React, { Component } from 'react';
export default class ApplicationsButtons extends Component {
render() {
var buttons = null;
let apps = this.props.apps;
let clickHandler = this.props.clickHandler;
if (apps.length > 0) {
buttons = apps.map(function(app) {
return (
<button
onClick={() => clickHandler(app.id)}
key={app.id}>
{app.name} - {app.id}
</button>
);
});
}
return (
<div>
{buttons}
</div>
);
}
}
Roles.js-
import React, { Component } from 'react';
export default class Roles extends Component {
render() {
var roles_li_elements = null;
let roles = this.props.roles;
console.log(roles);
if (roles.length > 0) {
roles_li_elements = roles.map(function(role) {
console.log(role);
return (
<li key={role.id}>
{role.name}
</li>
);
});
}
return (
<div>
<h4>Roles:</h4>
<ul>
{roles_li_elements}
</ul>
</div>
);
}
}
I want the Roles to update when the user clicks a button that picks a new app. Right now, clicking the buttons does update state.selected_app_id, but I need setRoles() to be called each time selected_app_id changes. I tried throwing it in the onClick:
updateSelectedApp(id) {
this.setState({selected_app_id: id});
this.setRoles();
}
for some reason that only changed the roles after clicking each button twice.
componentDidUpdate() {
this.updateApplicationData();
}
causes state to update forever in an infinite loop. You aren't supposed to update state inside componentWillUpdate.
updateSelectedApp(id) {
this.setState({selected_app_id: id}, () => {
this.setRoles();
});
}

Categories