I am doing my first real React app and I ran into a bug I can't seem to fix.
I have a card component somewhere in my app that, when it gets clicked it maximizes to take up a bigger section of the screen. It is called in MainSelectionButton.js as follows:
export default function MainSelectionButtons(props) {
const [maximizeCard, setMaximizeCard] = useState('nothing')
if (maximizeCard === 'nothing') {
return <ImageButton
title={props.title}
icon={props.icon}
whatIsMaximized={setMaximizeCard}
/>
}
else return <MaximizedPlot
title={maximizeCard}
whatIsMaximized={setMaximizeCard}
cryptoData={props.cryptoData}
daysChangeHandler={props.daysChangeHandler}
days={props.days}
/>
}
MaximizedPlot looks like this:
export default function MaximizedPlot(props) {
const sorter = (item) => {
switch (item) {
case 'Strategy Plot':
return strategyPlot;
case 'Value Plot':
return valuePlot
case 'Relative Plot':
return relativePlot;
default: break
}
}
const daysChangeHandler = (newDays) => {
props.daysChangeHandler(newDays)
}
const xButtonHandler = () => {
props.whatIsMaximized('nothing')
}
const title = (props.days === 0) ? 'the maximal number of days' : props.days
return <Card backgroundColor='#192734' isMaximized='maximized' >
<XButton onClick={() => xButtonHandler()}>x</XButton>
<h1>{props.title + ' over ' + title}</h1>
<img src={sorter(props.title)} width='100%' alt=''></img>
<NumberOfDays daysChangeHandler={daysChangeHandler}
cryptoData={props.cryptoData}>
</NumberOfDays>
</Card>
}
This component looks like this:
Maximized card component
With the XButton component on the top right corner and the NumberOfDays component being the input field at the bottom of the graph.
export default function App() {
const [cryptoData, setCryptoData] = useState({ timestamp: [], price: [], long_ma: [], short_ma: [], money: [], action: [] })
const [hours, setHours] = useState(744)
useEffect(() => {
getData(30 * 24).then(response => setCryptoData(response))
}, []);
const daysChangeHandler = (days) => {
setHours(days * 24, getData(hours).then(response => setCryptoData(response)))
console.log('days Changed!! new:', days)
}
return (
<div className="App">
<Card isMaximized=''>
<MainSelectionButtons title={'Strategy Plot'} icon={iconStrategy} cryptoData={cryptoData} daysChangeHandler={daysChangeHandler} days={hours / 24}></MainSelectionButtons>
<MainSelectionButtons title={'Value Plot'} icon={iconValue} cryptoData={cryptoData} daysChangeHandler={daysChangeHandler} days={hours / 24}></MainSelectionButtons>
<MainSelectionButtons title={'Relative Plot'} icon={iconRelative} cryptoData={cryptoData} daysChangeHandler={daysChangeHandler} days={hours / 24}></MainSelectionButtons>
</Card>
<Card>
is running
</Card>
</div>
);
}
EDIT: the user input component, called NumberOfDays looks like this:
export default function NumberOfDays(props) {
const [typedDays, setTypedDays] = useState(30)
const inputDaysChangeHandler = (event) => {
setTypedDays(event.target.value)
}
const submitHandler = () => {
props.daysChangeHandler(typedDays)
}
const predefinedDaysHandler = (days) => {
props.daysChangeHandler(days)
}
return <div>
<div className='input-form'>
Enter number of days to display:
<input type='number'
onChange={inputDaysChangeHandler}
className='days-number-input' />
<input
type='submit'
className='submit-button'
onClick={() => submitHandler()} />
or:
<button className='submit-button' onClick={() => predefinedDaysHandler(0)}>Max</button>
<button className='submit-button' onClick={() => predefinedDaysHandler(30)}>30 days</button>
<button className='submit-button' onClick={() => predefinedDaysHandler(240)} >10 days</button>
</div>
</div>
}
THE PROBLEM
Whenever I click on a button in the user input field (so 'submit', 'max', etc.) the window gets minimized. I can't figure out why, it just closes every time. What am I doing wrong? Is there something fundamentally wrong with my code or is it a little logical mistake that I am missing?
Thank you in advance for the help, I spent already more time on it than I'd like to admit.
UPDATE:
If anyone has this problem, It is solved if instead of using props, you use context.
Related
The input loses its focus when I start typing a character. I saw many StackOverflow answers but none of them is working. I have added unique keys also. What is the reason the code is not working? Without the state, it is working fine. But after adding the state, the input loses the focus.
import React, { useState } from "react";
const Footer = ({ formData }) => {
const [colorsArray, setColors] = useState(["Red", "Green", "Blue", "Yellow"]);
const [sizeArray, setSizes] = useState(["S", "M", "L", "XL"]);
const [sizeInput, setsizeInput] = useState("");
const colorElementRemoveHandler = (indexToRemove) => {
const filteredValue = colorsArray.filter((data, index) => {
return indexToRemove !== index;
});
setColors(filteredValue);
};
const sizeElementRemoveHandler = (indexToRemove) => {
const filteredValue = sizeArray.filter((data, index) => {
return indexToRemove !== index;
});
setSizes(filteredValue);
};
const addColorHandler = (e) => {
let input = e.target.value.toLowerCase();
if (input.length > 2) {
let temp = colorsArray;
temp.push(input);
setColors(temp);
}
};
const addSizeHandler = (e) => {
let input = e.target.value.toUpperCase();
if (input.length > 0) {
let temp = sizeArray;
temp.push(input);
setSizes(temp);
console.log(sizeArray);
}
};
const Test = () => {
return (
<input
type="text"
onChange={(e) => {
setsizeInput(e.target.value);
}}
value={sizeInput}
/>
);
};
const VariantUI = () => {
return (
<div>
<label>Size</label>
<input
id="optionName"
type="text"
placeholder="e.g S, M, L, XL"
onChange={(e) => {
setsizeInput(e.target.value);
}}
value={sizeInput}
/>
</div>
<ul>
{sizeArray.map((data, index) => {
return (
<li key={index}>
{data}
<i onClick={() => {sizeElementRemoveHandler(index);}}></i>
</li>
);
})}
</ul
);
};
return (
<VariantUI formData={formData} />
);
};
export default Footer;
Thanks in advance.
const Footer = ({ formData }) => {
// ..
const VariantUI = () => {
// ...
return (<VariantUI formData={formData} />)
}
You are creating a brand new type of component (VariantUI), in the middle of rendering Footer. This will happen on ever render. Each VariantUi function might have the same text as the previous one, but it's a different function, and thus to react it's a different type of component. Since it's a different type of component, the old one unmounts, and the new one mounts. A newly-mounted <input> does not have focus.
Component types must be defined only once, not on ever render. So VariantUI needs to be moved outside of footer. Since you're currently relying on closure variables, you will need to changes those to props:
const VariantUI = ({
sizeArray, setSizes, sizeInput, setSizeInput, // I might have missed a couple props
}) => {
// ...
}
const Footer = ({ formData }) => {
// ...
return (
<VariantUI
sizeArray={sizeArray}
setSizes={setSizes}
sizeInput={sizeInput}
setSizeInput={setSizeInput}
/>
);
}
I have two components.
First is called: BucketTabs
Second is called:BucketForms
To have a better idea. Below pictures illustrate it.
When I switching tab, different form will be showed below.
Q: Whenever I switch from one tab to other tab, and then switch back, the content in the previous BucketForms will be gone. But, gone data are supposed to be stored into a state of that BucketForms.
In fact, I've memo the BucketForms already, so I've expected the content(data) would not be gone.
What's the problem and how could I prevent the data to be gone after switching tab.
My BucketTabs:
import { BucketForms } from '~components/BucketForms/BuckForms'
export const BucketTabs: React.FC = () => {
const items = useMemo<ContentTabsItem[]>((): ContentTabsItem[] => {
return [
{
title: '1',
renderContent: () => <BucketForms key="1" bucketCategory="1" />,
},
{
title: '2',
renderContent: () => <BucketForms key="2" bucketCategory="2" />,
},
]
}, [])
return (
<div className="row">
<div className="col">
<ContentTabs items={tabs} kind="tabs" />
</div>
</div>
)
}
BucketForms
function PropsAreEqual(prev, next) {
const result = prev.bucketCategory === next.bucketCategory;
return result;
}
interface IData {
portfolioValue?: number
}
export const BucketForms: React.FC<IProps> = React.memo(props => {
const { bucketCategory } = props
const [data, setData] = useState<IData>({
})
const view = ({
portfolioValue,
}: IData) => {
return (
<>
<div className="row portfolio">
<FormNumericInput
key="input-portfolio-value"
name="portfolioValue"
required
value={portfolioValue}
/>
</div>
</>
)
}
return (
<Form
onChange={e => {
setData({ ...data, ...e, })
}}
>
{view(data)}
</Form>
)
}, PropsAreEqual)
Hello everyone and thank you for reading this! Here is my problem that i can't solve:
My application has the following functionality:
There are 2 inputs, then a button, when clicked, 2 more inputs appear and a button to send data from all inputs to the console, however, in the additional field, one input is required. This is where my problem arises: now, if I called additional inputs and filled in all the data, they are transferred to the console, if I didn’t fill in the required field, an error message goes to the console, BUT. I also need, in the event that I did NOT call additional inputs, the data of 2 basic inputs was transferred to the console. At the moment I can't figure it out.
import React, { useState } from "react";
import ReactDOM from "react-dom/client";
import produce from "immer";
const FunctionalBlock = ({
id,
idx,
isDeleted,
toggleBlockState,
additionalValue,
additionalTitle,
setNewBlock,
index,
}) => {
return (
<div
style={{
display: "flex",
maxWidth: "300px",
justifyContent: "space-between",
}}
>
{!isDeleted ? (
<React.Fragment>
<strong>{idx}</strong>
<input
type="text"
value={additionalTitle}
onChange={(e) => {
const additionalTitle = e.target.value;
setNewBlock((currentForm) =>
produce(currentForm, (v) => {
v[index].additionalTitle = additionalTitle;
})
);
}}
/>
<input
type="text"
value={additionalValue}
onChange={(e) => {
const additionalValue = e.target.value;
setNewBlock((currentForm) =>
produce(currentForm, (v) => {
v[index].additionalValue = additionalValue;
})
);
}}
/>
<button onClick={toggleBlockState}>now delete me</button>
</React.Fragment>
) : (
<button onClick={toggleBlockState}>REVIVE BLOCK</button>
)}
</div>
);
};
const Application = () => {
const [newBlock, setNewBlock] = useState([]);
const [firstInput, setFirstInput] = useState("");
const [secondInput, setSecondInput] = useState("");
const getNewBlock = (idx) => ({
id: Date.now(),
idx,
isDeleted: false,
additionalValue: "",
additionalTitle: "",
});
const toggleIsDeletedById = (id, block) => {
if (id !== block.id) return block;
return {
...block,
isDeleted: !block.isDeleted,
};
};
const createOnClick = () => {
const block = getNewBlock(newBlock.length + 1);
setNewBlock([...newBlock, block]);
};
const toggleBlockStateById = (id) => {
setNewBlock(newBlock.map((block) => toggleIsDeletedById(id, block)));
};
const showInputData = () => {
newBlock.map((item) => {
if (item.additionalTitle.length < 3) {
console.log("it is less than 3");
} else if (!item.additionalTitle && !item.additionalValue) {
console.log(firstInput, secondInput);
} else {
console.log(
firstInput,
secondInput,
item.additionalTitle,
item.additionalValue
);
}
});
};
return (
<div>
<div>
<input
type="text"
value={firstInput}
onChange={(e) => {
setFirstInput(e.target.value);
}}
/>
<input
type="text"
value={secondInput}
onChange={(e) => {
setSecondInput(e.target.value);
}}
/>
</div>
<div>
<button onClick={createOnClick}>ADD NEW INPUTS</button>
</div>
<div>
{newBlock.map((block, index) => (
<FunctionalBlock
key={index}
{...block}
toggleBlockState={() => toggleBlockStateById(block.id)}
setNewBlock={setNewBlock}
index={index}
/>
))}
</div>
<button onClick={showInputData}>send data</button>
</div>
);
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Application />);
Here is this code on sandbox for those who decided to help me. Thank you!
https://codesandbox.io/s/vigilant-booth-xnef6t
I'm having a big struggle with filtering an object of an array of objects by its ID in React. Let me explain:
The App is a Notes app, that stores every note you create with its Title, Text(name) and created date. The key is the ID.
Now I'm trying to create a popup modal every time I click on a note, which I managed to do ok, except for one thing: when the modal appears, it doesn't show only the selected note but all the notes list. I've tried with different array methods to filter the note I need, but didn't succeed.
This is the App.js file:
import React, { useState } from 'react';
import './App.css';
import Form from './components/Form';
import List from './components/List';
import { v4 as uuidv4 } from 'uuid';
import Modal from 'react-modal';
import ModalList from './components/ModalList';
Modal.setAppElement('#root');
function App() {
/*HOOKS */
const [list, setList] = useState([]);
const [modalList, setModalList] = useState([]);
//for modal:
let subtitle;
const [modalIsOpen, setIsOpen] = React.useState(false);
/*FUNCTIONS */
//add new notes
function handleAdd(title, name) {
if (name) {
const newList = list.concat({ title: title, name: name, id: uuidv4(), date: getCurrentDate() });
setList(newList);
console.log(newList);
const newModalList = list.concat({ title: title, name: name, id: uuidv4(), date: getCurrentDate() });
setModalList(newModalList);
}
else { alert("You should complete the notes field") }
}
//get the date for adding the note
function getCurrentDate() {
let newDate = new Date()
let date = newDate.getDate();
let month = newDate.getMonth() + 1;
let year = newDate.getFullYear();
let hours = newDate.getHours();
let minutes = newDate.getMinutes();
return `${month < 10 ? `0${month}` : `${month}`}/${date}/${year}, ${hours}:${minutes < 10 ? `0${minutes}` : `${minutes}`} hs.`
}
//deleting a note
function del(x) {
if (window.confirm("Do you really want to delete this item? The action is permanent.")) {
const newList = list.filter((item) => item.id !== x);
setList(newList);
}
}
//opening a modal
function openModal() {
setIsOpen(true);
}
//after opening a modal
function afterOpenModal() {
// references are now sync'd and can be accessed.
subtitle.style.color = '#f00';
}
//closing a modal
function closeModal() {
setIsOpen(false);
}
/*APP */
return (
<>
<div>
{/* MODAL */}
<Modal
isOpen={modalIsOpen}
onAfterOpen={afterOpenModal}
onRequestClose={closeModal}
style={customStyles}
contentLabel="Example Modal"
>
{modalList.map((item) => { return <ModalList key={item.id} item={item} quit={closeModal} /> })}
</Modal>
</div>
{/* FORM */}
<div className='App'>
<Form handleNew={handleAdd} />
</div>
{/* NOTES LIST */}
<div className='notes-list'>
{list.map((item) => { return <List key={item.id} item={item} quit={del} addModal={openModal} /> })}
</div>
</>
);
}
export default App;
And this is the ModalList.jsx file:
const ModalList = (props) => {
const { item, quit} = props;
/*LIST */
return (
<li ><button className='delete' onClick={()=>quit(item.id)}>x</button><p className='note-title'>{item.title}</p><p>{item.date}</p><p className='note-name'>{item.name}</p> </li>
);
}
export default ModalList;
I know I have to someway filter the object by its ID so that only appears what I clicked and not all the existing elements in the list, but I'm not finding the way.
Thanks a lot!
You are using Array.map here which is doing what it's supposed to do (listing the items), instead you should be using Array.filter which would return the ModalItem you need
{list.map((item) => { return <List key={item.id} item={item} quit={del} addModal={openModal} /> })}
openModal needs pass the item you clicked as a parameter and pass it to the callback.
Something like:
{list.map((item) => { return <List key={item.id} item={item} quit={del} addModal={() => openModal(item)} /> })}
Then openModal function needs to pass that parameter to the Modal component. To achieve that you can store it in your modalList for instance via setModalList([item])
I'm starting studying React and I was following this YouTube tutorial of a TO DO LIST using React.
https://www.youtube.com/watch?v=E1E08i2UJGI
My form loads perfectly, but if I write something and press any button I get the message: "completedTask is not a function". The same goes for buttons that call a function 'removeTask' and 'setEdit'.
I don't understand the reason I'm getting such error message. In the tutorial it works when the buttons are clicked. I've read in some forums that it would be related to the fact that you can't use map on Objects (non-array elements), but I didn't understand it very well and I don't know how to fix it. And the most mysterious parte: why does his code work and mine do not?
Could anybody please explain it?
Obs1: I found in another post that return tasks.tasks.map((task, index) solved the problem for 'task.map is not a function' error message in TASK.JS. I'm using it instead of return tasks.map((task, index) but I also didn't understant the reason.
Obs2: I don't think it makes any difference for the error message, but I used the button tag instead using React Icons as the video suggests.
=== TASKLIST.JS ===
import React, { useState } from 'react'
import Task from './Task'
import TaskForm from './TaskForm'
function TaskList() {
const [tasks, setTasks] = useState([]);
const addTask = task => {
if(!task.text || /^\s*$/.test(task.text)) {
return
}
const newTasks = [task, ...tasks];
setTasks(newTasks);
};
const updateTask = (taskId, newValue) => {
if(!newValue.text || /^\s*$/.test(newValue.text)) {
return
}
setTasks(prev => prev.map(item => (item.id === taskId ? newValue : item)));
};
const removeTask = id => {
const removeArr = [...tasks].filter(task => task.id !== id);
setTasks(removeArr)
};
const completedTask = id => {
let updatedTasks = tasks.map(task => {
if (task.id === id) {
task.isComplete = !task.isComplete
}
return task
})
setTasks(updatedTasks);
};
return (
<div>
<h1>Cabeçalho</h1>
<TaskForm onSubmit={addTask}/>
<Task
tasks={tasks}
completedTask={completedTask}
removeTask={removeTask}
updateTask={updateTask} />
</div>
)
}
export default TaskList
=== TASK.JS ===
import React, { useState } from 'react'
import TaskForm from './TaskForm'
function Task(tasks, completedTask, removeTask, updateTask) {
const [edit, setEdit] = useState({
id: null,
value: ''
})
const submitUpdate = value => {
updateTask(edit.id, value)
setEdit({
id: null,
value: ''
})
}
if (edit.id) {
return <TaskForm edit={edit} onSubmit={submitUpdate} />;
}
return tasks.tasks.map((task, index) => (
<div className={task.isComplete ? 'task-row complete' : 'task-row'} key={index}>
{task.text}
<div className="buttons">
<button onClick={() => completedTask(task.id)} className='completed-icon'>done</button>
<button onClick={() => removeTask(task.id)} className='delete-icon'>delete</button>
<button onClick={() => setEdit({id: task.id, value: task.text})} className='edit-icon'>edit</button>
</div>
</div>
))
};
export default Task
=== TASKFORM.JS ===
import React, { useState, useEffect, useRef } from 'react'
function TaskForm(props) {
const [input, setInput] = useState(props.edit ? props.edit.value : '');
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus()
})
const handleChange = e => {
setInput(e.target.value);
}
const handleSubmit = e => {
e.preventDefault();
props.onSubmit({
id: Math.floor(Math.random() * 1000),
text: input
});
setInput('');
};
return (
<form className="task-form" onSubmit={handleSubmit}>
{props.edit ? (
<>
<input type="text" placeholder="Update your task" value={input} name="text" className="task-input" onChange={handleChange} ref={inputRef}/>
<button className="task-button edit" onChange={handleChange}>Update a task</button>
</>
) : (
<>
<input type="text" placeholder="Add a new task" value={input} name="text" className="task-input" onChange={handleChange} ref={inputRef}/>
<button className="task-button" onChange={handleChange}>Add a task</button>
</>
)}
</form>
)
}
export default TaskForm
Try this:
function Task({ tasks, completedTask, removeTask, updateTask }) {
// ...
}
You can also do this (semantically equivalent):
function Task(props) {
const { tasks, completedTask, removeTask, updateTask } = props;
// ...
}
As mentioned here:
The first parameter will be props object itself. You need to destructure the object.
You can read more about object destructuring here.