Given :
function App() {
const [xPos, setXPos ] = React.useState(0);
const [style, setStyle] = React.useState({transform: `translateX(${xPos}px)`});
const onClick =(direction) => {
(direction === "left") ? setXPos(x => x-100) : setXPos(x => x +100);
setStyle({transform: `translateX(${xPos}px)`});
console.log(xPos)
}
return (
<div className="main_container">
<button className="left_button" onClick={() => onClick("left")}>slide left</button>
<div className="forecast_slider" >
<div className="forecast_container" style={style} >
{forecastBuilder()}
</div>
</div>
<button className="right_button" onClick={() => onClick("right")}>slide right</button>
</div>
)
}
const forecastBuilder = () => {
const cell = [];
for(var i = 1 ; i < 8 ; i++){
cell.push(
<div className={i}>
{i}
<img src="https://imgs.michaels.com/MAM/assets/1/5E3C12034D34434F8A9BAAFDDF0F8E1B/img/0E9397ED92304202B4A25D7387A74515/M10118706_2.jpg" width="100" height="80" border="1px solid black" />
<br></br>
<span>day {i}</span>
</div>
)
}
return cell;
}
ReactDOM.render(<App />, document.querySelector("#app"));
.main_container {
display:flex;
}
.forecast_container {
display: flex;
width: 510px;
height: 130px;
margin-left: auto;
margin-right: auto;
align-items: center;
text-align: center;
transition: transform 250ms;
}
.forecast_slider {
background-color: black;
color: white;
overflow:hidden;
float:right;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>
with JSFiddle link here ,
I want to make the translateX() animation increment and decrement normally. Currently, I have to click twice a button to change the direction. I have no clue why this is happening. I tried setting the initial style's transform parameter to 0px. I haven't tried other things since honestly, I am short of ideas, this bug is beyond my understanding of React.
Does anyone have any idea how I could solve this?
The problem is you try to use the updated xPos state in your onClick handler right after you "updated" it: setStyle({transform: `translateX(${xPos}px)`})
Don't forget that useState is asynchronous just like setState in class components. You can't update the state on one line and assume it's already changed on the next one. You'll likely use the unchanged state.
Create a new variable and update both states using that one:
function App() {
const [xPos, setXPos] = React.useState(0);
const [style, setStyle] = React.useState({
transform: `translateX(${xPos}px)`,
});
const onClick = (direction) => {
let x = direction === 'left' ? xPos - 100 : xPos + 100
setXPos(x)
setStyle({ transform: `translateX(${x}px)` });
console.log(xPos);
};
return (
<div className="main_container">
<button className="left_button" onClick={() => onClick('left')}>
slide left
</button>
<div className="forecast_slider">
<div className="forecast_container" style={style}>
{forecastBuilder()}
</div>
</div>
<button className="right_button" onClick={() => onClick('right')}>
slide right
</button>
</div>
);
}
const forecastBuilder = () => {
const cell = [];
for (var i = 1; i < 8; i++) {
cell.push(
<div className={i}>
{i}
<img
src="https://imgs.michaels.com/MAM/assets/1/5E3C12034D34434F8A9BAAFDDF0F8E1B/img/0E9397ED92304202B4A25D7387A74515/M10118706_2.jpg"
width="100"
height="80"
border="1px solid black"
/>
<br></br>
<span>day {i}</span>
</div>
);
}
return cell;
};
ReactDOM.render(<App />, document.querySelector('#app'));
.main_container {
display: flex;
}
.forecast_container {
display: flex;
width: 510px;
height: 130px;
margin-left: auto;
margin-right: auto;
align-items: center;
text-align: center;
transition: transform 250ms;
}
.forecast_slider {
background-color: black;
color: white;
overflow: hidden;
float: right;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Also, you could simply get rid of the useStyles hook as it's just some extra fluff around xPos:
function App() {
const [xPos, setXPos] = React.useState(0);
return (
<div className="main_container">
<button className="left_button" onClick={() => setXPos(xPos - 100)}>
slide left
</button>
<div className="forecast_slider">
<div className="forecast_container" style={{ transform: `translateX(${xPos}px)` }}>
{forecastBuilder()}
</div>
</div>
<button className="right_button" onClick={() => setXPos(xPos + 100)}>
slide right
</button>
</div>
);
}
const forecastBuilder = () => {
const cell = [];
for (var i = 1; i < 8; i++) {
cell.push(
<div className={i}>
{i}
<img
src="https://imgs.michaels.com/MAM/assets/1/5E3C12034D34434F8A9BAAFDDF0F8E1B/img/0E9397ED92304202B4A25D7387A74515/M10118706_2.jpg"
width="100"
height="80"
border="1px solid black"
/>
<br></br>
<span>day {i}</span>
</div>
);
}
return cell;
};
ReactDOM.render(<App />, document.querySelector('#app'));
.main_container {
display: flex;
}
.forecast_container {
display: flex;
width: 510px;
height: 130px;
margin-left: auto;
margin-right: auto;
align-items: center;
text-align: center;
transition: transform 250ms;
}
.forecast_slider {
background-color: black;
color: white;
overflow: hidden;
float: right;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Answer was found.
Setting a state is not synchronous and there is no reason to store style in an additional state hook since it's just one parameter and can be passed in the render function imemdiately.
const [xPos, setXPos ] = React.useState(0);
const onClick =(direction) => {
(direction === "left") ? setXPos(x => x-100) : setXPos(x => x +100);
}
return (
<div className="main_container">
<button className="left_button" onClick={() => onClick("left")}>slide left</button>
<div className="forecast_slider" >
<div className="forecast_container" style={{transform : `translateX(${xPos}px)`}} >
{forecastBuilder()}
</div>
</div>
<button className="right_button" onClick={() => onClick("right")}>slide right</button>
</div>
)
fixes the problem.
Related
I am creating an app where I get a sentence from my server, randomly rearrange the letters, and display the randomized sentence on the client with the appropriate amount of input fields = to the number of letters in the sentence. I am trying to add all the input fields using a ref to iterate over all input fields so when the user types a letter the will get moved to the next input field. I am still a little confused as to how I can achieve this.
my js file:
const App = () => {
const [sentence, setSentence] = useState('')
const [score, setScore] = useState(0)
const [words, setwords] = useState([])
const [letters, setLetters] = useState([])
const [guess, setGuess] = useState()
const inputs = useRef([]);
useEffect(() =>{
axios.get('http://localhost:3001/sentence/1').then( res =>{
console.log(res.data.data.sentence)
setSentence(res.data.data.sentence)
setGuess(new Array(res.data.data.sentence.length - 1))
setwords(res.data.data.sentence.split(' '))
})
}, [])
useEffect(() =>{
words.forEach((word, index) => {
if(index !== words.length - 1){
words[index] = word + ' '
setLetters(prev => [...prev, word + ' '])
}
else{
setLetters(prev => [...prev, word])
}
})
},[words])
const handleChange = (event, temp) =>{
console.log(inputs)
}
const handleEvent = () => {
}
let temp = -1
return(
<div className="main-content">
<div className='sentence-scramble'>
<ScrambleSentence sentence={sentence}/>
</div>
<div className='text'>
The yellow blocks are meant for spaces
</div>
<div className='score'>
Score: {score}
</div>
{words ?
words.map((word, idx)=>{
return(
<div className='input-guesser' key={idx}>
{word.split('').map((letter, index)=>{
if(letter === ' '){
temp += 1
return(
<input
className='space-inputs'
maxLength= '1'
key={index}
onChange={(event) => handleChange(event, temp)}
onKeyDown={(event) => handleEvent(event, index)}
ref={el => inputs.current[temp] = el}
/>
)
}
else{
temp +=1
return(
<input
className='input-fields'
maxLength= '1'
key={index}
onChange={(event) => handleChange(event, temp)}
onKeyDown={(event) => handleEvent(event, index)}
ref={el => inputs.current[temp] = el}
/>
)
}
})}
</div>
)
})
:null}
</div>
)
}
export default App;
my SCSS file:
.main-content{
height: 75vh;
width: 60%;
background-color: gray;
position: absolute;
top: 12.5%;
left: 23.5%;
display: flex;
flex-direction: column;
.sentence-scramble{
margin-top:25px;
text-align: center;
}
.text{
margin-top: 25px;
text-align: center;
}
.score{
text-align: center;
}
.input-guesser{
width: 100%;
display: flex;
margin-bottom: 10px;
.input-fields{
width: 100%;
margin-left: 5px;
margin-right: 5px;
border-radius: 5px;
text-align: center;
}
.space-inputs{
width: 100%;
margin-left: 5px;
margin-right: 5px;
border-radius: 5px;
background-color: #ffb74d;
text-align: center;
}
}
}
and for some reason, only the last input field is captured while all other input fields all null. Any help is appreciated!
Why not just use a querySelector and store all the inputs there? it seems not very react-ish way but it'll work just fine
useEffect(() => {
const parent = document.querySelector(".inputs-parent");
const allInputs = parent.querySelectorAll("input");
inputsRef.current = allInputs;
}, [])
I am working in a project using React Js. When I wrote this, Syntax: movieRef.current.style.transform = translateX(210px); it's not working. It shows an error- can't read 'style'. How can I solve this problem. I have attached my codes here.
import React, { useState, useRef } from "react";
import { useGetMoviesQuery } from "../../services/movieApi";
import SingleMovie from "../SingleMovie/SingleMovie";
import "./MovieRow.scss";
import "../SingleMovie/SingleMovie.scss";
import { MdArrowForwardIos, MdArrowBackIos } from "react-icons/md";
// const MovieRow = ({title}) => {codes}
const MovieRow = (props) => {
const { title, fetchURI } = props;
const { data, isLoading } = useGetMoviesQuery(fetchURI);
const movieRef = useRef();
// console.log("data:", data);
if (isLoading) {
return (
<div>
<h1 style={{ textAlign: "center", marginTop: "250px" }}>Loading...</h1>
</div>
);
}
const handleClick = (direction) => {
if (direction === "left") {
movieRef.current.style.transform = `translateX(210px)`;
}
};
return (
<>
<div className="movie-row-container">
<h1>{title}</h1>
<div className="wrapper">
<MdArrowForwardIos
className="slider right-arrow"
onClick={() => handleClick("right")}
/>
<div className="movie-row-block" ref={movieRef}>
{data?.results.map((movie, index) => (
<SingleMovie key={movie?.id} singleMovie={movie} index={index} />
))}
</div>
<MdArrowBackIos
className="slider left-arrow"
onClick={handleClick("left")}
/>
</div>
</div>
</>
);
};
export default MovieRow;
//MovieRow.scss file
h1 {
font-family: sans-serif;
font-size: 100%;
color: white;
}
// non-global css
.movie-row-container {
margin-top: 25px;
margin-left: 22px;
.wrapper {
position: relative;
.slider {
height: 100%;
width: 70px;
background-color: rgba(12, 42, 214, 0.438);
color: white;
position: absolute;
z-index: 99;
margin: 0 auto;
cursor: pointer;
&.right-arrow {
right: 0;
}
&.left-arrow {
left: 0;
}
}
.movie-row-block {
display: flex;
overflow-x: scroll;
overflow-y: hidden;
transform: translateX(210px);
}
}
.movie-row-block::-webkit-scrollbar {
display: none;
}
}
This part is firing infinitely even before the first render, that's why you'll get undefined on style:
<MdArrowBackIos
className="slider left-arrow"
onClick={handleClick("left")}
/>
Change it to this:
<MdArrowBackIos
className="slider left-arrow"
onClick={handleClick.bind(undefined, "left")}
/>
I'm very new to React so any advice would be appreciated on how to move an agent thumbnail to the teamComp div when it is clicked.
I'm also lost as to how to tackle filtering the data through a dropdown menu. Like how would I update the page without refreshing so that only the agents with the selected roles appear.
Anything would help, like I said before, I am a complete beginner to React and feel like I am underutilizing a lot of what makes React powerful.
App.js
import { useEffect, useMemo, useState } from "react";
import AgentCard from "./components/agentCard";
import Select from "react-select"
function App() {
const options = useMemo(
() => [
{value: "controller", label: "Controller"},
{value: "duelist", label: "Duelist"},
{value: "initiator", label: "Initiator"},
{value: "sentinel", label: "Sentinel"},
],
[]
);
const [agentDetails, setAgentDetails] = useState([]);
const getAllAgents = async () => {
const res = await fetch("https://valorant-api.com/v1/agents/");
const results = await res.json();
const agentNames = [],
agentImages = [],
agentRoles = [],
agentDetails = [];
for (let i = 0; i < Object.keys(results["data"]).length; i++) {
if (results["data"][i]["developerName"] != "Hunter_NPE") {
agentNames.push(results["data"][i]["displayName"]);
agentImages.push(results["data"][i]["displayIcon"]);
agentRoles.push(results["data"][i]["role"]["displayName"]);
}
else {
continue;
}
}
for (let i = 0; i < agentNames.length; i++) {
agentDetails[i] = [agentNames[i], [agentImages[i], agentRoles[i]]];
}
agentDetails.sort();
setAgentDetails(agentDetails);
};
useEffect(() => {
getAllAgents();
}, []);
return (
<div className="app-container">
<h2>Valorant Team Builder</h2>
<div className="teamComp">
</div>
<Select options={options} defaultValue={options} isMulti/>
<div id="agent_container" className="agent-container">
{agentDetails.map((agentDetails) => (
<AgentCard
img={agentDetails[1][0]}
name={agentDetails[0]}
role={agentDetails[1][1]}
/>
))}
</div>
</div>
);
}
export default App;
agentCard.js
import React from 'react'
const agentCard = ({role, name, img}) => {
return (
<div className="card-container">
<div className="img-container">
<img src={img} alt={name} />
</div>
<div className="info">
<h3 className="name">{name}</h3>
<small className="role"><span>Role: {role}</span></small>
</div>
</div>
)
}
export default agentCard
index.css
#import url('https://fonts.googleapis.com/css?family=Muli&display=swap');
#import url('https://fonts.googleapis.com/css?family=Lato:300,400&display=swap');
* {
box-sizing: border-box;
}
body {
background: #EFEFBB;
background: -webkit-linear-gradient(to right, #D4D3DD, #EFEFBB);
background: linear-gradient(to right, #D4D3DD, #EFEFBB);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-family: 'Lato';
margin: 0;
}
h1 {
letter-spacing: 3px;
}
.agent-container {
display: flex;
flex-wrap: wrap;
align-items: space-between;
justify-content: center;
margin: 0 auto;
max-width: 1200px;
}
.app-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
padding: 3rem 0.5rem;
}
.card-container {
background-color: #eee;
border-radius: 20px;
box-shadow: 0 3px 15px rgba(100, 100, 100, 0.5);
margin: 10px;
padding: 20px;
text-align: center;
}
.card-container:hover {
filter: brightness(70%);
transition: all 150ms ease;
}
.img-container img {
margin-top: 1.5rem;
height: 128px;
width: 128px;
}
.name {
margin-bottom: 0.2rem;
}
.teamComp h3 {
float: left;
}
Moving cards
To move a card to a different list you need a new state array that will represent "the members of the team". Something like:
const [team, setTeam] = useState([]);
Render the items in team inside the "teamComp" <div>, the same way you do it in the agent container.
Then add the new function prop to the card and use it in the onClick handler in the card <div>:
<AgentCard
key={agentDetails[0]}
img={agentDetails[1][0]}
name={agentDetails[0]}
role={agentDetails[1][1]}
handleClick={moveToTeam}
/>
...
<div className="card-container" onClick={() => handleClick(name)}>
and in this function, add the agentDetails item to the team state and remove it from the agentDetails state. Make sure that you supply new arrays when setting state:
const moveToTeam = (name) => {
const newTeam = [...team, agentDetails.find((agent) => agent[0] === name)];
const newAgentDetails = agentDetails.filter((agent) => agent[0] !== name);
setTeam(newTeam);
setAgentDetails(newAgentDetails);
};
Filtering
For filtering you need another state that contains all selected options:
const [options, setOptions] = useState(allOptions);
where allOptions is an array of all available options, and it should not change.
Add the onChange handler to the <Select> component:
<Select
options={allOptions}
onChange={(selectedOptions) => setOptions(selectedOptions)}
defaultValue={allOptions}
isMulti
/>
and finally use options to filter cards:
<div id="agent_container" className="agent-container">
{agentDetails
.filter(
(agentDetails) =>
options.filter((option) => option.label === agentDetails[1][1])
.length > 0
)
.map((agentDetails) => (
<AgentCard
key={agentDetails[0]}
img={agentDetails[1][0]}
name={agentDetails[0]}
role={agentDetails[1][1]}
handleClick={moveToTeam}
/>
))}
</div>
You can see the complete example on codesandbox.
I left most of the names in place, although I think using agentDetails for different things is confusing. The data structures can also be improved, but I left them unchanged as well.
Following the screenshot below I'm trying to move the cursor through the string which I have no idea how to do.
I'm trying to achieve the effect of an old-phone UI. I'm already managed to make it blink.
I'm using ReactJs and styled-components. Follow the code below:
import console from 'console';
import { useContext, useEffect, useState } from 'react'
import { PhonewordsContext } from '../../PhonewordsContext';
import { Container, Keyboard, Screen, Cursor } from './styles'
export function Phone() {
const { getWords } = useContext(PhonewordsContext);
const [number, setNumber] = useState<string>('');
const [position, setPosition] = useState<number>(0);
useEffect(() => {
getWords(number)
},[number]); // #todo: warning
function onBtnClicked(char: string) {
// in case is not in the end of string add substring in the index
if (position !== number.length){
setNumber(number.slice(0, position) + char + number.slice(position))
} else {
setNumber(`${number}${char}`)
}
setPosition(position +1)
}
function onRemoveChar() {// #todo: how remove words box when empty. re-render?
const rightPosition = position - 1
if (position > 0) {
// concatenate slices of the string before and after the current index
setNumber(number.slice(0, rightPosition) + number.slice(rightPosition + 1))
setPosition(position -1)
}
}
function onUpClicked() {
// position never be negative
if (position > 0)setPosition(position - 1)
}
function onDownClicked() {
// check for max position
if (position < number.length) setPosition(position + 1)
}
return (
<Container>
<Screen>
{/* MOVE CURSOR */}
<span>
{number.split('').map(i =>
alert(`here ${i}`)
)}
</span>
<Cursor />
{number}
</Screen>
{position}
<Keyboard>
<button onClick={() => onUpClicked()}>⬆</button>
<button onClick={() => onDownClicked()}>⬇</button>
<button onClick={() => onRemoveChar()}>⌫</button>
<button disabled>1</button>
<button onClick={() => onBtnClicked('2')}>2 abc</button>
<button onClick={() => onBtnClicked('3')}>3 def</button>
<button onClick={() => onBtnClicked('4')}>4 ghi</button>
<button onClick={() => onBtnClicked('5')}>5 jkl</button>
<button onClick={() => onBtnClicked('6')}>6 mno</button>
<button onClick={() => onBtnClicked('7')}>7 pqrs</button>
<button onClick={() => onBtnClicked('8')}>8 tuv</button>
<button onClick={() => onBtnClicked('9')}>9 wxyz</button>
<button disabled>*</button>
<button disabled>0 ⌴</button>
<button disabled>#</button>
</Keyboard>
</Container>
)
}
and the css file using styled-components:
import styled from "styled-components"
export const Container = styled.div`
display: flex;
align-items: center;
flex-direction: column;
width: 100%;
`
export const Screen = styled.div`
padding: 1rem 2rem;
border: 0;
border-radius: 0.25rem;
background: var(--white);
width: 15rem;
height: 8rem;
`
export const Keyboard = styled.div`
display: grid;
padding: 2rem 0;
grid-template-columns: repeat(3, 64px);
grid-template-rows: 32px repeat(4, 64px);
gap: 8px;
button {
border-radius: 0.25rem;
border: 0;
box-shadow: #777 2px 1px 10px 0px, rgba(255, 255, 255, 0.8) -6px -2px 16px 0px;
transition: 0.4s;
&:active {
box-shadow: 2px 2px 2px #777;
transform: translateY(3px);
}
}
`
export const Cursor = styled.span`
animation: blink 1.5s linear infinite alternate;
border-color: #333;
border-left: 1px solid;
margin-left: -1px;
#keyframes blink {
50% {
opacity: 0;
}
100% {
opacity: 1;
}
}
`
Thanks for any help!
You should substring number based Cursor position.
if (position == 0)
<Cursor /> {number}
else if(position > 0)
{number.substring(0, position)} <Cursor /> {number.substring(position + 1, number.length)}
like this.
<Screen>
{/* MOVE CURSOR */
<span>{number.slice(0, position)} <Cursor /> {number.slice(position)}</span>
</Screen>
Managed to implement it using the solution above!
THE PHOTO BELOW SHOWS WHAT I WANT TO ACHIEVE. Basically I have a component where when I hover some arrows (up and down) appears but when the user click those arrows the background color changes, but just on the click itself. and the background color does not remain clicked. I tried to achieve that with a setTimeout on the click event. I can let the timer work on the click but the clearTimeout is not working. Any clues? the code is also below (after the photo).
THIS IS MY CODE:
//rafce
import React, { useState } from 'react';
// styled components
import styled from 'styled-components';
// icons
import { IoIosArrowUp, IoIosArrowDown } from 'react-icons/io';
const DurationIntervalComponent = () => {
const [hours, setHours] = useState('0');
const [showHoursArrows, setShowHourArrows] = useState(false);
const [arrowActiveUp, setArrowActiveUp] = useState(false);
const [arrowActiveDown, setArrowActiveDown] = useState(false);
const incrementHours = (value) => {
let timer = setTimeout(() => setArrowActiveUp(true), 500);
clearTimeout(timer, 1000)
setHours((prevHours) => {
// if there is nothing
if (!prevHours) {
return '0';
} else if (+prevHours >= 24) {
return '0';
} else {
return String(+prevHours + 1);
}
});
};
const decrementHours = (value) => {
setArrowActiveDown(true);
setHours((prevHours) => {
// if there is nothing
if (!prevHours) {
return '0';
} else if (+prevHours <= 0) {
return '24';
} else {
return String(+prevHours - 1);
}
});
};
return (
<Container>
<Row>
<p> Interval* </p>
<Inputs>
<Selection
onMouseEnter={() => setShowHourArrows(true)}
onMouseLeave={() => setShowHourArrows(false)}
>
{showHoursArrows && (
<p
className="icon"
onClick={incrementHours}
style={
arrowActiveUp
? { backgroundColor: 'red' }
: { backgroundColor: 'none' }
}
>
<IoIosArrowUp />
</p>
)}
<SquareInput value={hours} />
<p>hours</p>
{showHoursArrows && (
<p className="icon" onClick={decrementHours}>
<IoIosArrowDown />
</p>
)}
</Selection>
<div>
<SquareInput />
<p>minutes</p>
</div>
</Inputs>
</Row>
<hr />
<Row>
<p> Duration* </p>
<Inputs>
<SquareInput />
<p>:</p>
<SquareInput />
<p>to</p>
<SquareInput />
<p>:</p>
<SquareInput />
</Inputs>
</Row>
</Container>
);
};
const Container = styled.div`
border: 1px solid #c5c5c5;
border-radius: 15px;
`;
const Row = styled.div`
display: flex;
align-items: center;
height: 150px;
justify-content: space-between;
padding: 20px;
`;
const Inputs = styled.div`
display: flex;
align-items: center;
p {
margin-right: 5px;
font-size: 0.8em;
text-align: center;
}
`;
const Selection = styled.div`
.icon {
font-size: 1.2em;
padding: 0;
}
`;
const SquareInput = styled.input`
width: 50px;
height: 50px;
border: 1px solid #c5c5c5;
border-radius: 10px;
outline: none;
margin-right: 5px;
font-size: 1.5em;
text-align: center;
`;
export default DurationIntervalComponent;