I'm trying out the new react for functions without class and i'm trying to use setstate but it's not working, saying undefined if i used it straight or saying it's not a function if i remove this. from it.
here is my code
import './App.css';
import React, { useState } from 'react';
function App() {
var [sum1, setSum1] = useState(0);
var [sum2, setSum2] = useState(0);
function handleClick(props) {
let buttonText = props.target.value;
let inputText = document.getElementById('result').value;
console.log(inputText + buttonText);
// let sum1 =setState;
// let sum2 =setState;
let total = inputText + buttonText;
if (total.charAt(0) === "0") {
} else {
document.getElementById("result").value = total;
}
if (props.target.value === "clear") {
document.getElementById("result").value = "";
}
if (props.target.value === "+") {
sum1 =document.getElementById("result").value.slice(0, -1);
setSum1({sum1:parseFloat(sum1)});
// alert(sum1);
document.getElementById("result").value = "";
alert(sum1);
}
if (props.target.value === "=") {
sum2 =document.getElementById("result").value.slice(0, -1);
setSum2({sum2:parseFloat(sum2)});
alert(sum1+sum2);
document.getElementById("result").innerText = sum1+sum2;
}
return total;
}
// function handleClick2(props) {
// document.getElementById("ans").innerHTML = eval(document.getElementById("result").value);
// }
return (
<div className="App">
<div id="ans" className="answer">Ans = 0</div>
<input type="text" onChange={handleClick} readOnly placeholder="0" className="inputid" id="result"/>
<hr/>
{/* calculator buttons below */}
{/* button + */}
<button className="opbut" value="+" onClick={handleClick}>+</button>
{/* button - */}
<button className="opbut" value="-" onClick={handleClick}>-</button>
{/* button × */}
<button className="opbut" value="*" onClick={handleClick}>×</button>
{/* button ÷ */}
<button className="opbut" value="/" onClick={handleClick}>÷</button>
{/* button 0 */}
<button className="calbut" value={0} onClick={handleClick}>0</button>
{/* button 1 */}
<button className="calbut" value={1} onClick={handleClick}>1</button>
{/* button 2 */}
<button className="calbut" value={2} onClick={handleClick}>2</button>
{/* button 3 */}
<button className="calbut" value={3} onClick={handleClick}>3</button>
{/* button 4 */}
<button className="calbut" value={4} onClick={handleClick}>4</button>
{/* button 5 */}
<button className="calbut" value={5} onClick={handleClick}>5</button>
{/* button 6 */}
<button className="calbut" value={6} onClick={handleClick}>6</button>
{/* button 7 */}
<button className="calbut" value={7} onClick={handleClick}>7</button>
{/* button 8 */}
<button className="calbut" value={8} onClick={handleClick}>8</button>
{/* button 9 */}
<button className="calbut" value={9} onClick={handleClick}>9</button>
{/* button ( */}
<button className="opbut" value="clear" onClick={handleClick}>C</button>
{/* button ) */}
<button className="opbut" value="Del" onClick={handleClick}>Del</button>
{/* button % */}
<button className="opbut" value="%" onClick={handleClick}>%</button>
{/* button y^ */}
<button className="opbut" value="^" onClick={handleClick}>y^</button>
{/* button total = */}
<button className="ansbut" value="=" onClick={handleClick}>=</button>
<hr/>
<div className="appName">A Simple React Calculator - Jayflo</div>
</div>
);
}
export default App;
above is the code....
as i'm trying to select a button, and onclick operator selected, setstate and delete textfield then accept another input and operate both input...
React has its own particular way of updating the DOM based on the state of (value of) the elements, so you can't use native element methods.
In this example we use three hooks: useState, useEffect, and useRef.
useState maintains the state of the input element, the updated calculation, and the final evaluation which is passed into the result div.
useEffect ensures that after the result has been completed we clear the calc state.
useRef is React's way of identifying an element. In this case, after each button is clicked, the input element is focused on again.
Here's a working example based on your code.
const { useRef, useEffect, useState } = React;
function Example() {
const [ input, setInput ] = useState('');
const [ calc, setCalc ] = useState('');
const [ result, setResult ] = useState('');
const inputRef = useRef();
// Update the input state when the value is changed
function handleInput(e) {
const { value } = e.target;
setInput(value);
}
// When the input is no longer focused (we've clicked
// a button) update the `calc` state
function handleBlur(e) {
const { value } = e.target;
setCalc(prev => prev + value);
}
// When we click a button, either update the `calc` state,
// or if we click "=", update the `result` state
function handleClick(e) {
const { nodeName, value } = e.target;
if (nodeName === 'BUTTON') {
setInput('');
inputRef.current.focus();
switch(value) {
case '=': {
setResult(eval(calc));
break;
}
case 'clear': {
setCalc('');
break;
}
default: {
setCalc(prev => prev + value);
break;
}
}
}
}
// When the result is updated, clear the `calc` state
useEffect(() => setCalc(''), [result]);
return (
<div class="container">
<div>
<div>{`Result: ${result}`}</div>
<div>{`Calc: ${calc}`}</div>
<input autoFocus ref={inputRef} onBlur={handleBlur} onChange={handleInput} type="text" value={input} />
</div>
<div class="buttons" onClick={handleClick}>
<button value="+">+</button>
<button value="-">-</button>
<button value="*">×</button>
<button value="/">÷</button>
<button value="0">0</button>
<button value="1">1</button>
<button value="2" >2</button>
<button value="3">3</button>
<button value="4">4</button>
<button value="5">5</button>
<button value="6">6</button>
<button value="7">7</button>
<button value="8">8</button>
<button value="9">9</button>
<button value="clear">C</button>
<button value="Del">Del</button>
<button value="%">%</button>
<button value="^">y^</button>
<button value="=">=</button>
</div>
</div>
);
};
// Render it
ReactDOM.render(
<Example />,
document.getElementById("react")
);
.container { display: flex; width: 200px; height: 200px; }
.buttons {
padding: 10px;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-gap: 2px;
}
button {
background: rgba(0, 0, 0, 0.5);
border: 1px solid #111;
padding: 10px;
color: #EEE;
border-radius: 5px;
font-size: 12px;
cursor: pointer;
}
<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="react"></div>
Alternative method which reduces the number of states and doesn't have to use useRef because now there's no input.
const { useRef, useEffect, useState } = React;
function Example() {
const [ calc, setCalc ] = useState('');
const [ result, setResult ] = useState('');
// When we click a button, either update the `calc` state,
// or if we click "=", update the `result` state
function handleClick(e) {
const { nodeName, value } = e.target;
if (nodeName === 'BUTTON') {
switch(value) {
case '=': {
setResult(eval(calc));
break;
}
case 'clear': {
setCalc('');
break;
}
default: {
setCalc(prev => prev + value);
break;
}
}
}
}
// When the result is updated, clear the `calc` state
useEffect(() => setCalc(''), [result]);
return (
<div class="container">
<div class="results">
<div>{`Result: ${result}`}</div>
<br />
<div>{`Calc: ${calc}`}</div>
</div>
<div class="buttons" onClick={handleClick}>
<button value="+">+</button>
<button value="-">-</button>
<button value="*">×</button>
<button value="/">÷</button>
<button value="0">0</button>
<button value="1">1</button>
<button value="2" >2</button>
<button value="3">3</button>
<button value="4">4</button>
<button value="5">5</button>
<button value="6">6</button>
<button value="7">7</button>
<button value="8">8</button>
<button value="9">9</button>
<button value="clear">C</button>
<button value="Del">Del</button>
<button value="%">%</button>
<button value="^">y^</button>
<button value="=">=</button>
</div>
</div>
);
};
// Render it
ReactDOM.render(
<Example />,
document.getElementById("react")
);
.container { display: flex; }
.results { width: 40px; }
.buttons {
margin-left: 6em;
padding: 10px;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-gap: 2px;
}
button {
background: rgba(0, 0, 0, 0.5);
border: 1px solid #111;
padding: 10px;
color: #EEE;
border-radius: 5px;
font-size: 12px;
cursor: pointer;
}
<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="react"></div>
Let's say you want to keep track of a pending state
To make use of useState, first you have to import it
import { useState } from "react";
Then,
const [ isPending , setIsPending ] = useState(false);
the value you pass to the useState(initialValue) function will be the initial value of the state.
You can pass any value like array string or object to the useState() function
To set/change the value of the state you declared
setIsPending(true);
The value of your state is held by the isPending variable
UPDATED: About your Calculator
What you're showing is mixing react and HTML and Vanilla JS. It's not well seen and it's harder to maintain. So, the react approach is available in this new code sandbox:
(It's only handling the Sum, as an example purpose of how we can do this application using only react)
https://codesandbox.io/s/falling-star-hf90r
About USE STATE
You can see one example working here: https://codesandbox.io/s/intelligent-cori-5xi34?file=/src/App.js:50-759
The easiest way to do this is using hooks. Now, there are multiple hooks. In your case, you will need React.useState, it'll return 2 things: the value, the setter.
const [myValue, mySetter] = React.useState(defaultValue);
After that, everything will work almost the same, in this case, I've defined 3 states (sum1, sum2, and result) and 2 extra functions, handleResult and handleClear, which I could guess from your code.
export default function App() {
const [sum1, setSum1] = React.useState(0);
const [sum2, setSum2] = React.useState(0);
const [result, setResult] = React.useState(sum1 + sum2);
const handleCalculate = () => {
setResult(+sum1 + +sum2);
}
const handleClear = () => {
setResult(0);
setSum1(0);
setSum2(0);
}
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
Sum1: <input type="number" onChange={e => setSum1(e.target.value)} value={sum1}/> <br/>
Sum2: <input type="number" onChange={e => setSum2(e.target.value)} value={sum2}/> <br/>
<button onClick={handleCalculate}>Calculate</button>
<button onClick={handleClear}>Clear</button>
<h2>Result: {result}</h2>
<h3>Interactive: {+sum1 + +sum2}</h3>
</div>
);
}
function App() {
const [buttonText, setButtonText] = useState("InitialValue");
const [sum1, setSum1] = useState(0);
const [sum2, setSum2] = useState(0);
const handleClick = (props) =>{
setButtonText(props.target.value)
let inputText = document.getElementById('result').value;
console.log(inputText + buttonText);
let total = inputText + buttonText;
if (total.charAt(0) === "0") {
} else {
document.getElementById("result").value = total;
}
if (props.target.value === "clear") {
document.getElementById("result").value = "";
}
if (props.target.value === "+") {
setSum1(parseFloat(document.getElementById("result").value));
// alert(sum1);
document.getElementById("result").value = "";
}
if (props.target.value === "=") {
alert(sum1);
setSum2(parseFloat(document.getElementById("result").value))
document.getElementById("result").innerText = sum1+sum2;
}
return total;
}
return(
///my code
)
please try this now.
Related
Here is my function with arguments that i added in index.html in publics folder in a script tag
function displayContent(event, contentNameID) {
let content = document.getElementsByClassName("contentClass");
let totalCount = content.length;
for (let count = 0; count < totalCount; count++) {
content[count].style.display = "none";
}
let links = document.getElementsByClassName("linkClass");
totalLinks = links.length;
for (let count = 0; count < totalLinks; count++) {
links[count].classList.remove("active");
}
document.getElementById(contentNameID).style.display = "block";
event.currentTarget.classList.add("active");
}
Trying to call this function from click of buttons on my react component that looks like below
<button class="linkClass" onclick="displayContent(event, 'project2')">Meet at Campus
</button>
Please guide me with the syntax
Here's the correct syntax
<button className="linkClass" onClick={(event)=>displayContent(event,'project2')}>Meet at Campus</button>
Edit: please note that React components return JSX
It looks like you're trying to make some sort accordion but you shouldn't really be mixing vanilla JS with React as React needs control of the DOM.
So here's a brief example of how you might approach this using 1) state, and 2) a Panel component which comprises a button, and some content.
const { useState } = React;
function Example() {
// Initialise state with an array of false values
const [ state, setState ] = useState([
false, false, false
]);
// When a button in a panel is clicked get
// its id from the dataset, create a new array using `map`
// and then set the new state (at which point the component
// will render again
function handleClick(e) {
const { id } = e.target.dataset;
const updated = state.map((el, i) => {
if (i === id - 1) return true;
return false;
});
setState(updated);
}
// Pass in some props to each Panel component
return (
<div>
<Panel
name="Panel 1"
active={state[0]}
id="1"
handleClick={handleClick}
>
<span className="text1">Content 1</span>
</Panel>
<Panel
name="Panel 2"
active={state[1]}
id="2"
handleClick={handleClick}
>
<span className="text2">Content 2</span>
</Panel>
<Panel
name="Panel 3"
active={state[2]}
id="3"
handleClick={handleClick}
>
<span className="text3">Content 3</span>
</Panel>
</div>
);
}
function Panel(props) {
// Destructure those props
const {
name,
id,
active,
handleClick,
children
} = props;
// Return a div with a button, and
// content found in the children prop
// When the button is clicked the handler is
// called from the parent component, the state
// is updated, a new render is done. If the active prop
// is true show the content otherwise hide it
return (
<div className="panel">
<button data-id={id} onClick={handleClick}>
{name}
</button>
<div className={active && 'show'}>
{children}
</div>
</div>
);
}
ReactDOM.render(
<Example />,
document.getElementById('react')
);
.panel button:hover { cursor: pointer; }
.panel { margin: 1em 0; }
.panel div { display: none; }
.panel div.show { display: block; margin: 1em 0; }
.add { margin-top: 1em; background-color: #44aa77; }
.text1 { color: darkblue; font-weight: 600; }
.text2 { color: darkgreen; font-weight: 700; }
.text3 { color: darkred; font-weight: 300; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Can't you use
document.getElementById("linkClass").onclick = () =>{
displayContent();
}
by giving the element an id with same of the class?
Given :
function App() {
var xPos = 0;
const [style, setStyle] = React.useState({transform: `translateX(${xPos}px)`});
const onClick =(direction) => {
(direction === "left") ? xPos -= 100 : xPos += 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 upon respective button click. Currently, I suspect that when I call setStyle() hook, the component gets rerendered such that the line
var xPos=0;
is read again. I was not able to find a way to increment or decrement in another way (without beforehand assigning the value of 0 such that style = {style} on the first render ignores the parameter).
Does anyone have any idea how I could solve this?
The problem is that the value of xPos is going to be set as 0 on every render, so you are not saving it's new value, it gets reset on every render.
You should store the xPos in the state as well.
const [xPos, setXpos] = useState(0)
and then increment / decrement in the function itself:
const onClick = (direction) => {
(direction === "left") ? setXpos(x => x - 100) : setXpos(x => x + 100)
}
This should work
I have element with width 400% and I want to move it to left by using translateX(-(index/4)*100%) when focused index changes.
Changing focused element translateX property with tab keyboard button displays it wrong on middle elements (1,2) even though using same hardcoded styling works as expected. What am I missing here?
const {useState} = React;
const App = () => {
const [curr, setCurr] = useState(0);
const carouselStyles = {
transform: `translateX(${-(curr / 4) * 100}%)`
// uncomment to see that styling works fine with hardcoded values 1,2..
// transform: `translateX(${-(1 / 4) * 100}%)`
};
const handleFocus = (num) => {
if (num !== curr) {
setCurr(num);
}
};
console.log(carouselStyles);
return (
<div>
<div className="carousel" style={carouselStyles}>
<div className="item">
11 very long text
<a href="/111" onFocus={() => handleFocus(0)}>
11111
</a>
</div>
<div className="item">
22 very long text
<a href="/222" onFocus={() => handleFocus(1)}>
22222
</a>
</div>
<div className="item">
33 very long text
<a href="/333" onFocus={() => handleFocus(2)}>
33333
</a>
</div>
<div className="item">
44 very long text
<a href="/444" onFocus={() => handleFocus(3)}>
44444
</a>
</div>
</div>
current: {curr}
</div>
);
}
// Render it
ReactDOM.render(
<App />,
document.getElementById("react")
);
.carousel {
display: flex;
align-items: center;
width: 400%;
}
.item {
flex: 0 1 100%;
text-align: center;
border: 1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
I needed to prevent the scrolling and in my provided example its enough to add this line into handleFocus function
window.scrollTo(0, 0);
But in my real scenario parent wrapper also had overflow: hidden; which prevented above code from working. So I've used refs
const handleFocus = (num) => {
if (num !== curr) {
setCurr(num);
carouselRef.current.scrollTo(0, 0);
}
};
return (
<div ref={carouselRef}>
<div className="carousel" style={carouselStyles}>
...
</div>
current: {curr}
</div>
);
I need to list out a long name list inside my page while showing all names at first is not desirable.
So I try to add an expand more button on it.
However, using a button will keep the browser focus on that button after it's pressed, left the button position unchanged on the screen while the name was inserted before that button.
On the other hand, using any, not focusable element (eg. div with onclick function) will do the desired behavior but lost the accessibility at all. Making the "button" only clickable but not focusable.
How do I make the button flushed to list bottom like the snippet div block does? Or is there a better choice to expand the existing list?
const myArray = [
'Alex',
'Bob',
'Charlie',
'Dennis',
'Evan',
'Floron',
'Gorgious',
'Harris',
'Ivan',
'Jennis',
'Kurber',
'Lowrance',
]
const ExpandList = (props) => {
const [idx, setIdx] = React.useState(8)
const handleExpand = e => {
setIdx(idx + 1)
}
return <div className='demo'>
<h1>Name List</h1>
{myArray.slice(0,idx).map(
name => <p key={name}>{name}</p>
)}
<div>
<button onClick={handleExpand} children='Button Expand' className='pointer' />
<div onClick={handleExpand} className='pointer'>Div Expand</div>
</div>
</div>
}
ReactDOM.render(<ExpandList/>, document.getElementById('root'))
.demo>p {
display: block;
padding: 20px;
color: #666;
background: #3331;
}
.demo>div>div {
display: flex;
padding: 15px;
margin-left: auto;
color: #666;
background: #3331;
}
.pointer {
cursor: pointer;
}
.pointer:hover {
background-color: #6663;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id='root' class='demo'>hello</div>
Removing focus from the button in the click handler is probably the most elegant approach: e.target.blur(). It will work on any HTML element, whether it is focusable or not (as with the div in your case).
const myArray = [
'Alex',
'Bob',
'Charlie',
'Dennis',
'Evan',
'Floron',
'Gorgious',
'Harris',
'Ivan',
'Jennis',
'Kurber',
'Lowrance',
]
const ExpandList = (props) => {
const [idx, setIdx] = React.useState(8)
const handleExpand = e => {
e.target.blur()
setIdx(idx + 1)
}
return <div className='demo'>
<h1>Name List</h1>
{myArray.slice(0,idx).map(
name => <p key={name}>{name}</p>
)}
<div>
<button onClick={handleExpand} children='Button Expand' className='pointer' />
<div onClick={handleExpand} className='pointer'>Div Expand</div>
</div>
</div>
}
ReactDOM.render(<ExpandList/>, document.getElementById('root'))
.demo>p {
display: block;
padding: 20px;
color: #666;
background: #3331;
}
.demo>div>div {
display: flex;
padding: 15px;
margin-left: auto;
color: #666;
background: #3331;
}
.pointer {
cursor: pointer;
}
.pointer:hover {
background-color: #6663;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id='root' class='demo'>hello</div>
Inspired by #MiKo, temporally unmount the button after click and set a timeout to add it back seems to do the work. Since browser lose the focus on original expand button, this will keep content flush down without focusing the original button:
const ExpandList = (props) => {
const [idx, setIdx] = React.useState(8)
const [showBtn, setShowBtn] = React.useState(true)
const handleExpand = e => {
setShowBtn(false)
setIdx(idx + 1)
setTimeout(() => setShowBtn(true), 10)
}
return <div className='demo'>
<h1>Name List</h1>
{myArray.slice(0,idx).map(
name => <p key={name}>{name}</p>
)}
{showBtn?
<div>
<button onClick={handleExpand} children='Button Expand' className='pointer' />
<div onClick={handleExpand} className='pointer'>Div Expand</div>
</div> :
<div></div>
}
</div>
}
But I'm still looking a method that doesn't need to 'unmount' a thing which should be there all time.
I've three components with the following tree:
<Update>
<ExpenseItem>
<ExpenseItemModal>
Update takes an array of expenses and render a ExpenseItem component for each expense.
I'm using an hook to handle modal visibility. As you can expect, i'm using this modal to edit the expense attributes.
A toggle method is imported from useModal hook on ExpenseItem to open and close the modal. What I expect is to click outside of the modal and close it. But if I've another ExpenseItem with the modal set to true, it will close the current, but it will still show the other one. I want to click outside of the modal (maybe on Update component) and close all modals at once, to avoid multiple modals opened. Actually I want only on modal open at once.
These are the following components:
Upload
import { useState, useEffect } from 'react';
import useModal from '../hooks/useModal';
import ExpenseItem from './expenseItem';
import axios from 'axios';
function Update({ data }) {
useEffect(() => console.log('update component', expenses));
const saveToDatabase = () => {
axios.post('http://localhost:3001/expenses', expenses).then((res) => {
console.log('data is saved to database');
});
};
const { setIsShowing } = useModal();
const closeModals = () => setIsShowing(false);
const [ expenses, setExpenses ] = useState(data);
return (
<div>
{expenses.map((expense, index) => {
return <ExpenseItem key={index} index={index} expenses={expenses} setExpenses={setExpenses} />;
})}
<button onClick={() => saveToDatabase()}>Save</button>
</div>
);
}
export default Update;
ExpenseItem
import useModal from '../hooks/useModal';
import EditExpenseModal from './editExpenseModal';
function ExpenseItem(props) {
const { isShowing, toggle, setIsShowing } = useModal();
let { description, date, credit, debit } = props.expenses[props.index];
const updateValue = (expense, setExpenses, success) => {
const expenses = [ ...props.expenses ];
expenses.splice(props.index, 1, {
...expense
});
setExpenses(expenses);
success();
};
return (
<div>
<div className="expense-box" onClick={toggle}>
<p>{date}</p>
<div className="expense-info">
<p className="expense-info--description">{description}</p>
<p className="expense-info--debit">{debit}</p>
<p className="expense-info--credit">{credit}</p>
</div>
</div>
<EditExpenseModal
isShowing={isShowing}
hide={toggle}
expense={props.expenses[props.index]}
updateExpense={updateValue}
setExpenses={props.setExpenses}
/>
<style jsx>{`
.expense-box {
width: 800px;
border: 1px solid black;
border-radius: 2px;
margin: 25px auto;
padding: 0 10px;
}
.expense-info {
display: flex;
}
.expense-info--description {
margin: 0 auto 0 0;
}
.expense-info--debit {
color: red;
}
.expense-info--credit {
color: green;
}
`}</style>
</div>
);
}
export default ExpenseItem;
EditExpenseModal
import { useState, useEffect, Fragment } from 'react';
import { createPortal } from 'react-dom';
const EditExpenseModal = ({ expense, isShowing, hide, updateExpense, setExpenses }) => {
const { description, date, credit, debit } = expense;
useEffect(() => {
document.body.style.overflow = 'hidden';
return () => (document.body.style.overflow = 'unset');
}, []);
const [ expenseItem, setExpenseItem ] = useState({
date,
description,
category: null,
subcategory: null,
credit,
debit
});
const handleInputChange = (e) => {
const { name, value } = e.target;
setExpenseItem({ ...expenseItem, [name]: value });
};
return isShowing
? createPortal(
<Fragment>
<div>
<div className="form">
<form>
<ul>
<li className="form-inputs">
<label>Date</label>
<input type="text" name="date" defaultValue={date} onChange={handleInputChange} />
</li>
<li className="form-inputs">
<label>Description</label>
<input
type="text"
name="description"
defaultValue={description}
onChange={handleInputChange}
/>
</li>
<li className="form-inputs">
<label>Category</label>
<input type="text" name="category" onChange={handleInputChange} />
</li>
<li className="form-inputs">
<label>Subcategory</label>
<input type="text" name="subcategory" onChange={handleInputChange} />
</li>
<li className="form-inputs">
<label>Credit</label>
<input
type="text"
name="credit"
defaultValue={credit}
onChange={handleInputChange}
/>
</li>
<li className="form-inputs">
<label>Debit</label>
<input
type="text"
name="debit"
defaultValue={debit}
onChange={handleInputChange}
/>
</li>
</ul>
</form>
<button onClick={() => updateExpense(expenseItem, setExpenses, hide)}>save</button>
<button onClick={hide}>close</button>
</div>
<style jsx>{`
.form {
background: grey;
display: flex;
flex-direction: column;
position: absolute;
height: 100vh;
top: 0;
right: 0;
width: 40%;
}
.form-inputs {
display: flex;
flex-direction: column;
list-style-type: none;
padding: 1rem 2rem;
}
`}</style>
</div>
</Fragment>,
document.body
)
: null;
};
export default EditExpenseModal;
useModal Hook
import { useState } from 'react';
const useModal = () => {
const [ isShowing, setIsShowing ] = useState(false);
function toggle() {
setIsShowing(!isShowing);
}
return {
isShowing,
setIsShowing,
toggle
};
};
export default useModal;
I don't mind to change these modal structure to make it work.
In this case, to avoid these scenarios you can write a separate method to close modal,
inside ExpenseItem.js
<EditExpenseModal
isShowing={isShowing}
hide={hideModal} //instead of toggle
...
>
and write hideModal method to close modal by passing directly 'false' value instead of using! operator.
like this in useModal Hook :
function hideModal() {
setIsShowing(false);
}