why is useEffect running on first render? - javascript

I have a MERN react component that is going to show a toast to my user after they create a new group.
Here is my useEffect below
useEffect(() => {
toast(
<Fragment>
New Group Created
</Fragment>
);
}, [successCreate]);
I only want this useEffect to run AFTER my user creates a new group.
It currently runs on first render, and after my user clicks the modal(child) then triggers the successCreate from redux(just creates a new group).
How can I make useEffect run only when successCreate is called.. NOT on first render AND successCreate
Here is my component
import React, { Fragment, useState, useEffect, useContext } from 'react';
import WizardInput from '../auth/wizard/WizardInput';
import {useDispatch, useSelector} from 'react-redux';
import {
Button,
Card,
CardBody,
Form,
Label,
Input,
Media,
Modal,
ModalBody,
ModalHeader
} from 'reactstrap';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import Select from 'react-select';
import { toast } from 'react-toastify';
import makeAnimated from 'react-select/animated';
import {listGroups, createGroup} from '../../actions/index';
//import { isIterableArray } from '../../helpers/utils';
import { CsvUploadContext } from '../../context/Context';
import chroma from 'chroma'
const StepOneForm = ({ register, errors, watch}) => {
const { upload, setUpload } = useContext(CsvUploadContext);
//const { handleInputChange } = useContext(CsvUploadContext);
const [ createGroupModal, setCreateGroupModal ] = useState(false)
const [newGroup, setNewGroup] = useState({
title: ''
})
const dispatch = useDispatch();
const groups = useSelector(state => state.groups)
const groupCreate = useSelector((state) => state.groupCreate)
const {success: successCreate} = groupCreate
const animatedComponents = makeAnimated();
useEffect(() => {
dispatch(listGroups())
}, [successCreate])
useEffect(() => {
toast(
<Fragment>
New Group Created
</Fragment>
);
}, [successCreate]);
const customStyles = {
control: (base, state) => ({
...base,
background: "light",
// match with the menu
borderRadius: state.isFocused ? "3px 3px 0 0" : 3,
// Overwrittes the different states of border
borderColor: state.isFocused ? "primary" : "light",
// Removes weird border around container
boxShadow: state.isFocused ? null : null,
"&:hover": {
// Overwrittes the different states of border
borderColor: state.isFocused ? "blue" : "#2c7be5"
}
}),
menu: base => ({
...base,
// override border radius to match the box
borderRadius: 0,
// kill the gap
marginTop: 0
}),
menuList: base => ({
...base,
// kill the white space on first and last option
padding: 0,
color: 'f9fafd'
}),
option: (styles, { data, isDisabled, isFocused, isSelected }) => {
const color = chroma(data.color);
return {
...styles,
backgroundColor: isDisabled
? null
: isSelected
? data.color
: isFocused,
color: '232e3c',
};
}
};
const toggle = () => { setCreateGroupModal(!createGroupModal)}
const closeBtn = (
<button className="close font-weight-normal" onClick={toggle}>
×
</button>
);
const handleSubmit = (e) => {
e.preventDefault()
dispatch(createGroup(newGroup))
setCreateGroupModal(false)
};
console.log(upload?.group)
const handleChange = e => {
setNewGroup({...newGroup, [e.target.name]: e.target.value})
}
return (
<Fragment>
<Media className="flex-center pb-3 d-block d-md-flex text-center mb-2">
<Media body className="ml-md-4">
</Media>
</Media>
<h4 className="mb-1 text-center">Choose Groups</h4>
<p className=" text-center fs-0">These groups will contain your contacts after import</p>
<Select
name="group"
required={true}
className="mb-3"
styles={customStyles}
components={animatedComponents}
innerRef={register({
required: true
})}
closeMenuOnSelect={true}
options={groups}
getOptionLabel={({title}) => title}
getOptionValue={({_id}) => _id}
onChange={(_id) => setUpload({...upload, group: _id})}
isMulti
placeholder="select group"
isSearchable={true}
errors={errors}
/>
<Button color="light" onClick={(() => setCreateGroupModal(true))} className="rounded-capsule shadow-none fs--1 ml- mb-0" >
<FontAwesomeIcon icon="user-plus" />
{` or create a new group`}
</Button>
<Modal isOpen={createGroupModal} centered toggle={() => setCreateGroupModal(!createGroupModal)}>
<ModalHeader toggle={toggle} className="bg-light d-flex flex-between-center border-bottom-0" close={closeBtn}>
Let's give the group a name
</ModalHeader>
<ModalBody className="p-0">
<Card>
<CardBody className="fs--1 font-weight-normal p-4">
<Card>
<CardBody className="fs--1 font-weight-normal p-4">
<Form onSubmit={handleSubmit}>
<Label for="title">Group Name:</Label>
<Input value={newGroup.title.value} onChange={handleChange} className="mb-3" name="title" id="title"/>
<Button block onClick={handleSubmit} color="primary" className="mb-3">Save</Button>
</Form>
</CardBody>
</Card>
<Button block onClick={() => setCreateGroupModal(false)}>close</Button>
</CardBody>
</Card>
</ModalBody>
</Modal>
<WizardInput
type="textarea"
label="or add number manually seperated by comma"
placeholder="+17209908576, +18165009878, +19138683784"
name="manual-add"
rows="4"
id="manual-add"
innerRef={register({
required: false
})}
errors={errors}
/>
</Fragment>
);
};
export default StepOneForm;

Is successCreate a boolean?
This would work:
useEffect(() => {
if(!successCreate) return;
toast(
<Fragment>
New Group Created
</Fragment>
);
}, [successCreate]);
useEffect is always called when the dependencies change - on first render it is guaranteed to be called because there is nothing previous - this is the equivalent to componentDidMount
useEffect(() => {
console.log('mounted');
},[]);

Related

onClick in React changes all of the classes states in react

I have creates a aside in react and I want to change their state to active when clicked on each of them, but when I click on any item all of them suddenly get activated. How to fix this?
import React, { useState, useEffect } from "react";
import { Col, Image, Row } from "react-bootstrap";
import "./Company.scss";
// * api
import { getCoin } from "../services/api";
// *spinner
import Loader from "./Loader";
const Company = () => {
const [changeClass, setChangeClass] = useState(false);
const [coins, setCoins] = useState([]);
useEffect(() => {
const fetchAPI = async () => {
const data = await getCoin();
setCoins(data);
};
fetchAPI();
}, []);
const myHandler = () => {
setChangeClass(true);
console.log("trued");
};
const myHandler2 = () => {
console.log();
};
return (
<>
{coins.length ? (
coins.map((coin) => (
<Row
className={
changeClass
? "p-2 border-top d-flex align-items-center company-list-single-active"
: "p-2 border-top d-flex align-items-center company-list-single"
}
onClick={() => {
myHandler();
myHandler2();
}}
key={coin.id}
>
<Col xxl="2" xl="2" lg="2" md="2" sm="2" xs="2">
<Image
src={coin.image}
alt={coin.name}
className="coin-image mx-2"
fluid
/>
</Col>
<Col>
<span>{coin.name}</span>
</Col>
</Row>
))
) : (
<Loader />
)}
</>
);
};
export default Company;
you use a test to display selected coin by the same state and it is toggled between true or false.
I added a state to save the selected coin and test on it on looping.
On the onClick we can directly update the selected item.
you cannot do that "coins.length ?" this will be always true if type of coins is array i added a test like "coins.length > 0 ?"
here the solution
import React, { useState, useEffect } from 'react';
import { Col, Image, Row } from 'react-bootstrap';
import './Company.scss';
// * api
import { getCoin } from '../services/api';
// *spinner
import Loader from './Loader';
const Company = () => {
const [selectedCoin, setSelectedCoin] = useState(null);
const [coins, setCoins] = useState([]);
useEffect(() => {
const fetchAPI = async () => {
const data = await getCoin();
setCoins(data);
};
fetchAPI();
}, []);
return (
<>
{coins.length > 0 ? (
coins.map((coin) => (
<Row
className={
selectedCoin === coin.id
? 'p-2 border-top d-flex align-items-center company-list-single-active'
: 'p-2 border-top d-flex align-items-center company-list-single'
}
onClick={() => setSelectedCoin(coin.id)}
key={coin.id}
>
<Col xxl="2" xl="2" lg="2" md="2" sm="2" xs="2">
<Image src={coin.image} alt={coin.name} className="coin-image mx-2" fluid />
</Col>
<Col>
<span>{coin.name}</span>
</Col>
</Row>
))
) : (
<Loader />
)}
</>
);
};
export default Company;

React - how to click outside to close the tooltip

This is my current tooltip.
I am using react-power-tooltip
When I click the button, I can close the tooltip.
But I want to close the tooltip when I click outside the tooltip.
How am I supposed to do it?
App.js
import "./styles.css";
import MoreHorizIcon from "#material-ui/icons/MoreHoriz";
import TooltipList from "./TooltipList";
import { useState } from "react";
export default function App() {
const [showTooltip, setShowTooltip] = useState(true);
return (
<div className="App">
<button
className="post-section__body__list__item__right__menu-btn"
onClick={() => {
setShowTooltip((x) => !x);
}}
style={{ position: "relative" }}
>
<MoreHorizIcon />
<TooltipList show={showTooltip} />
</button>
</div>
);
}
TooltipList
import React from "react";
import Tooltip from "react-power-tooltip";
const options = [
{
id: "edit",
label: "Edit"
},
{
id: "view",
label: "View"
}
];
function Tooptip(props) {
const { show } = props;
return (
<Tooltip
show={show}
position="top center"
arrowAlign="end"
textBoxWidth="180px"
fontSize="0.875rem"
fontWeight="400"
padding="0.5rem 1rem"
>
{options.map((option) => {
return (
<div
className="tooltop__option d-flex align-items-center w-100"
key={option.id}
>
{option.icon}
<span style={{ fontSize: "1rem" }}>{option.label}</span>
</div>
);
})}
</Tooltip>
);
}
export default Tooptip;
CodeSandbox:
https://codesandbox.io/s/optimistic-morning-m9eq3?file=/src/App.js
Update 1:
I update the code based on the answer.
It can now click outside to close, but if I click the button to close the tooltip, it's not working.
App.js
import "./styles.css";
import MoreHorizIcon from "#material-ui/icons/MoreHoriz";
import TooltipList from "./TooltipList";
import { useState } from "react";
export default function App() {
const [showTooltip, setShowTooltip] = useState(true);
return (
<div className="App">
<button
className="post-section__body__list__item__right__menu-btn"
onClick={() => {
setShowTooltip((x) => !x);
}}
style={{ position: "relative" }}
>
<MoreHorizIcon />
<TooltipList
show={showTooltip}
onClose={() => {
setShowTooltip();
}}
/>
</button>
</div>
);
}
TooltipList.js
import React, { useEffect, useRef } from "react";
import Tooltip from "react-power-tooltip";
const options = [
{
id: "edit",
label: "Edit"
},
{
id: "view",
label: "View"
}
];
function Tooptip(props) {
const { show, onClose } = props;
const containerRef = useRef();
useEffect(() => {
if (show) {
containerRef.current.focus();
}
}, [show]);
return (
<div
style={{ display: "inline-flex" }}
ref={containerRef}
tabIndex={0}
onBlur={(e) => {
onClose();
}}
>
<Tooltip
show={show}
position="top center"
arrowAlign="end"
textBoxWidth="180px"
fontSize="0.875rem"
fontWeight="400"
padding="0.5rem 1rem"
>
{options.map((option) => {
return (
<div
className="tooltop__option d-flex align-items-center w-100"
key={option.id}
>
{option.icon}
<span style={{ fontSize: "1rem" }}>{option.label}</span>
</div>
);
})}
</Tooltip>
</div>
);
}
export default Tooptip;
Codesandbox
https://codesandbox.io/s/optimistic-morning-m9eq3?file=/src/App.js:572-579
As I can see, you are using material-ui for icons, so there is an option known as ClickAwayListner within material-ui
App.js
import "./styles.css";
import MoreHorizIcon from "#material-ui/icons/MoreHoriz";
import ClickAwayListener from '#material-ui/core/ClickAwayListener';
import TooltipList from "./TooltipList";
import { useState } from "react";
export default function App() {
const [showTooltip, setShowTooltip] = useState(true);
const handleClickAway = () => {
setShowTooltip(false);
}
return (
<div className="App">
<ClickAwayListener onClickAway={handleClickAway}>
<button
className="post-section__body__list__item__right__menu-btn"
onClick={e => {
e.stopPropagation();
setShowTooltip((x) => !x);
}}
style={{ position: "relative" }}
>
<MoreHorizIcon />
<TooltipList show={showTooltip} />
</button>
</ClickAwayListener>
</div>
);
}
Wrap your container with ClickAwayListener
You should add a wrapper element to detect if the click is outside a component
then the showTooltip to false at your code:
codeSanbox
import "./styles.css";
import MoreHorizIcon from "#material-ui/icons/MoreHoriz";
import TooltipList from "./TooltipList";
import { useState, useEffect, useRef } from "react";
export default function App() {
const [showTooltip, setShowTooltip] = useState(true);
const outsideClick = (ref) => {
useEffect(() => {
const handleOutsideClick = (event) => {
if (ref.current && !ref.current.contains(event.target)) {
setShowTooltip(false);
}
};
// add the event listener
document.addEventListener("mousedown", handleOutsideClick);
}, [ref]);
};
const wrapperRef = useRef(null);
outsideClick(wrapperRef);
return (
<div className="App" ref={wrapperRef}>
<button
className="post-section__body__list__item__right__menu-btn"
onClick={() => {
setShowTooltip((x) => !x);
}}
style={{ position: "relative" }}
>
<MoreHorizIcon />
<TooltipList show={showTooltip} />
</button>
</div>
);
}
Here is a crazy little idea. I wrapped your component in an inline-flex div and gave it focus on load. Then added an onBlur event which will hide the menu if you click anywhere else. This can be used if you don't want to give focus on any other element on the page.
https://codesandbox.io/s/epic-kapitsa-yh7si?file=/src/App.js:0-940
import "./styles.css";
import MoreHorizIcon from "#material-ui/icons/MoreHoriz";
import TooltipList from "./TooltipList";
import { useRef, useState, useEffect } from "react";
export default function App() {
const [showTooltip, setShowTooltip] = useState(true);
const containerRef = useRef();
useEffect(() => {
containerRef.current.focus();
}, []);
return (
<div className="App">
<div
style={{ display: "inline-flex" }}
ref={containerRef}
tabIndex={0}
onBlur={(e) => {
debugger;
setShowTooltip(false);
}}
>
<button
className="post-section__body__list__item__right__menu-btn"
onClick={() => {
setShowTooltip((x) => !x);
}}
style={{ position: "relative" }}
>
<MoreHorizIcon />
<TooltipList show={showTooltip} />
</button>
</div>
</div>
);
}
Update 1:
The problem was your button click was called every time you select an item that toggles your state. I have updated the code to prevent that using a useRef that holds a value.
ToolTip:
import React from "react";
import Tooltip from "react-power-tooltip";
const options = [
{
id: "edit",
label: "Edit"
},
{
id: "view",
label: "View"
}
];
function Tooptip(props) {
const { show, onChange } = props;
return (
<>
<Tooltip
show={show}
position="top center"
arrowAlign="end"
textBoxWidth="180px"
fontSize="0.875rem"
fontWeight="400"
padding="0.5rem 1rem"
>
{options.map((option) => {
return (
<div
onClick={onChange}
className="tooltop__option d-flex align-items-center w-100"
key={option.id}
>
{option.icon}
<span style={{ fontSize: "1rem" }}>{option.label}</span>
</div>
);
})}
</Tooltip>
</>
);
}
export default Tooptip;
App
import "./styles.css";
import MoreHorizIcon from "#material-ui/icons/MoreHoriz";
import TooltipList from "./TooltipList";
import { useRef, useState, useEffect, useCallback } from "react";
export default function App() {
const [showTooltip, setShowTooltip] = useState(true);
const [onChangeTriggered, setonChangeTriggered] = useState(false);
const containerRef = useRef();
const itemClicked = useRef(false);
useEffect(() => {
containerRef.current.focus();
}, []);
return (
<div className="App">
<div
style={{ display: "inline-flex" }}
ref={containerRef}
tabIndex={0}
onBlur={(e) => {
debugger;
if (!onChangeTriggered) setShowTooltip(false);
}}
// onFocus={() => {
// setShowTooltip(true);
// }}
>
<button
className="post-section__body__list__item__right__menu-btn"
onClick={() => {
if (!itemClicked.current) setShowTooltip((x) => !x);
itemClicked.current = false;
}}
style={{ position: "relative" }}
>
<MoreHorizIcon />
<TooltipList
show={showTooltip}
onChange={useCallback(() => {
itemClicked.current = true;
}, [])}
/>
</button>
</div>
</div>
);
}
https://codesandbox.io/s/epic-kapitsa-yh7si
Enjoy !!

Deleting a child component in react

So first, this web-app is one of my first web-apps using react, and I have done it using JavaScript and C# .net core.
What I want to do is, on click of a Button i want to creat a component, that appears on screen.
And so far that part is working well.
import FusePageSimple from '#fuse/core/FusePageSimple';
import { makeStyles } from '#material-ui/core/styles';
import Typography from '#material-ui/core/Typography';
import React, {ParentComponent, useEffect, useRef,useState } from 'react';
import { useDispatch } from 'react-redux';
import LeftSideBar from './components/SideBarComp';
import RightSideComp from './components/RightSideComp';
import Hidden from '#material-ui/core/Hidden';
import Icon from '#material-ui/core/Icon';
import Button from '#material-ui/core/Button';
import IconButton from '#material-ui/core/IconButton';
import { tokensToRegExp } from 'path-to-regexp';
const useStyles = makeStyles(theme => ({
layoutRoot: {}
}));
function Dashboard(props) {
const [compCount, setCompCount] = useState(0);
const [component, setComponent] = useState('');
const eventhandler = data => {
if(data.eliminar === 1){
setComponent(component.filter((item) => item.props.RowNum !== data.RowNum));
}
};
const classes = useStyles(props);
const pageLayout = useRef(null);
const onAddChild = () => {
setCompCount(compCount + 1);
}
useEffect(() => {
const compArry = [...component];
if(compCount > 0 ){
compArry.push(<RightSideComp key={compCount} RowNum={compCount} onChange={eventhandler}/>)
}
setComponent(compArry);
}, [compCount]);
return (
<FusePageSimple
classes={{
root: classes.layoutRoot
}}
header={
<div className="flex flex-col flex-1">
<div className="flex items-center p-24 px-12">
<Hidden lgUp>
<IconButton
onClick={ev => pageLayout.current.toggleLeftSidebar()}
aria-label="open left sidebar"
>
<Icon>menu</Icon>
</IconButton>
</Hidden>
<div className="flex-1 lg:px-12">
<h4>Header</h4>
</div>
</div>
</div>
}
contentToolbar={
<div className="px-24">
<h4>Content Toolbar</h4>
</div>
}
content={
<div className="p-24">
<h4>Content</h4>
<br />
{component}
</div>
}
leftSidebarHeader={
<div className="p-24">
<h4>Sidebar Header</h4>
</div>
}
leftSidebarContent={
<div className="p-24">
<Button variant="contained" color="primary"
onClick={ev => onAddChild()}>
Novo
</Button>
{/* <Button variant="contained" color="primary">Copiar</Button> */}
<br />
<LeftSideBar />
</div>
}
innerScroll
ref={pageLayout}
/>
);
}
export default Dashboard;
Thats the code of the parent.
So by clicking the button "Novo"(Means "New" in english) I call the onAddChild(), I add To the compCount and after with the UseEffect I add the child component. I pass 2 props to that Child RowNum, the number of components that I am creating, and an event that is trigger on state change.
import React,{ useEffect, useState } from 'react';
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
import TextField from '#material-ui/core/TextField';
import { makeStyles } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
import Divider from '#material-ui/core/Divider';
const useStyles = makeStyles(theme => ({
root: {
'& .MuiTextField-root': {
margin: theme.spacing(2),
width: '25ch'
}
}
}));
function RightSideComp(props) {
const classes = useStyles();
const [component, setComponent] = useState({
dataInicio: (!(props.datastate) ? '' : props.datastate.dataInicio),
dataFim: (!(props.datastate) ? '' : props.datastate.dataFim),
descricao: (!(props.datastate) ? '' : props.datastate.descricao),
RowNum: props.RowNum,
eliminar: 0,
copiar: 0
});
console.log(component);
const handleChangeInput = event => {
const { name, value } = event.target;
setComponent(prevState => ({
...prevState,
[name]: value
}));
};
const handleChangeButtonCopiar = event => {
setComponent(preState => ({ ...preState, copiar: 1 }));
};
const handleChangeButtonEliminar = event => {
setComponent(preState => ({ ...preState, eliminar: 1 }));
};
useEffect(() => {
props.onChange(component);
}, [component]);
return (
<div>
<div className={classes.root}>
<TextField
id="dataInicio"
label="Data Inicio"
name="dataInicio"
type="datetime-local"
className={classes.textField}
value={component.dataInicio}
onChange={ev => handleChangeInput(ev)}
InputLabelProps={{
shrink: true
}}
/>
<TextField
id="dataFim"
name="dataFim"
label="Data do Fim"
type="datetime-local"
className={classes.textField}
value={component.dataFim}
onChange={ev => handleChangeInput(ev)}
InputLabelProps={{
shrink: true
}}
/>
<TextField
id="outlined-multiline-flexible"
label="Multiline"
name="descricao"
multiline
rowsMax={5}
value={component.descricao}
onChange={ev => handleChangeInput(ev)}
variant="outlined"
/>
<div>
<Button variant="contained" color="primary" onClick={ev => handleChangeButtonCopiar(ev)}>
Copiar
</Button>
<Button variant="contained" color="secondary" onClick={ev => handleChangeButtonEliminar(ev)}>
Eliminar
</Button>
</div>
</div>
<Divider />
</div>
);
}
export default React.memo(RightSideComp);
and thats my child component.
So my error is when I change the eliminar("delete" in english) that component gets deleted. And with the code I have, if it is the last row it deletes fine but, if it is one from the middle it delets that one and all the others that are below the one I clicked. I tried with slice and didnt work well too. I am missing something, but what?
thanks for your time
I think you need to use 'useRef'
const refsList = React.useRef([]);
then when you create something
compArry.push(<RightSideComp ref={refsList[compCount]} key={compCount} RowNum={compCount} onChange={eventhandler}/>)
now you can directly do anything you want with any elements based on this ref, eg. refsList[0].current will be the actual 1st element
refsList[0].current.remove()

Using axios to make use of a service not working

I'm trying to use S3 service to upload an image and it's telling me that certain variables aren't defined when I have defined them. I have imported axios and all the other stuff that are required
import React, { useState } from "react";
import ReactDOM from "react-dom";
import axios from "axios";
import Grid from "#material-ui/core/Grid";
import Button from "#material-ui/core/Button";
import Card from "#material-ui/core/Card";
import TextField from "#material-ui/core/TextField";
import CreateIcon from "#material-ui/icons/Create";
import Box from "#material-ui/core/Box";
import CardMedia from "#material-ui/core/CardMedia";
import MuiAlert from "#material-ui/lab/Alert";
import Snackbar from "#material-ui/core/Snackbar";
import { withStyles } from "#material-ui/core/styles";
import { makeStyles } from "#material-ui/core/styles";
import Chip from "#material-ui/core/Chip";
import Avatar from "#material-ui/core/Avatar";
import Slider from "#material-ui/core/Slider";
import Typography from "#material-ui/core/Typography";
import InputAdornment from "#material-ui/core/InputAdornment";
import { connect } from "react-redux";
function mapStateToProps(state) {
return {};
}
const useStyles = makeStyles((theme) => ({
root: {
"& > *": {
margin: theme.spacing(1),
marginLeft: theme.spacing(15),
},
},
input: {
display: "none",
},
}));
const useSliderStyles = makeStyles({
root: {
width: 250,
},
input: {
width: 100,
},
});
const UploadButton = () => {
const classes = useStyles();
return (
<div className={classes.root}>
<input
accept='image/*'
className={classes.input}
id='contained-button-file'
multiple
type='file'
/>
<label htmlFor='contained-button-file'>
<Button variant='contained' color='primary' component='span'>
Upload
</Button>
</label>
</div>
);
};
const StyledCard = withStyles({
root: { height: 600, width: 350 },
})(Card);
const PetitionForm = () => {
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [open, setOpen] = useState(false);
const [petition, validPetition] = useState(false);
const [noTitle, titleError] = useState(false);
const [noDescription, descriptionError] = useState(false);
const [hashtag, setHashtag] = useState("");
const [arrayOfHashtags, addHashtag] = useState([]);
const [money, setMoney] = React.useState(500);
const slider = useSliderStyles();
const handleTitleChange = (event) => setTitle(event.target.value);
const handleDescriptionChange = (event) => setDescription(event.target.value);
const handleClose = (event, reason) => {
if (reason === "clickaway") {
return;
}
};
const Alert = (props) => (
<MuiAlert elevation={6} variant='filled' {...props} />
);
const clearField = (event) => {
setOpen(true);
if (title.length > 0 && description.length > 0) {
validPetition(true);
setTitle("");
setDescription("");
addHashtag([]);
setHashtag("");
axios({
url: `call/s3/backend`,
method: "post",
data: {
images: imageArray.toByteArray(),
},
})
.then((res) => {
imageUrlArr = res.data;
axios({
url: `api/petition_posts`,
method: "post",
data: {
petition_post: {
title: title,
description: description,
hashtags: arrayOfHashtags.join(" "),
amount_donated: 0,
media: imageUrlArr,
goal: money,
card_type: "petition",
org_profile_id: 1,
},
},
})
.then((res) => {
console.log(res.data);
})
.catch((error) => console.log(error));
})
.catch((error) => console.log(error));
}
titleError(true ? title.length === 0 : false);
descriptionError(true ? description.length === 0 : false);
};
const handleDelete = (h) => () => {
addHashtag((arrayOfHashtags) =>
arrayOfHashtags.filter((hashtag) => hashtag !== h)
);
};
const handleHashtagChange = (event) => setHashtag(event.target.value);
const handleSliderChange = (event, newValue) => {
setMoney(newValue);
};
const handleInputChange = (event) => {
setMoney(event.target.value === "" ? "" : Number(event.target.value));
};
const newHashtag = () => {
if (arrayOfHashtags.length < 3) {
addHashtag((arrayOfHashtags) => arrayOfHashtags.concat(hashtag));
} else {
console.log("Too many hashtags");
}
};
const Hashtags = arrayOfHashtags.map((h) => (
<Chip
key={h.length}
size='small'
avatar={<Avatar>#</Avatar>}
label={h}
onDelete={handleDelete(h)}
/>
));
return (
<StyledCard>
<Box mt={1}>
<Grid container justify='center'>
<TextField
id='outlined-multiline-static'
multiline
rows={1}
variant='outlined'
placeholder='Title'
value={title}
onChange={handleTitleChange}
helperText={
open // only displays helper text if button has been clicked and fields haven't been filled
? !noTitle || petition
? ""
: "Can't be an empty field"
: ""
}
/>
</Grid>
</Box>
<Box mt={1}>
<Grid container justify='center'>
<CardMedia title='Petition'>
<UploadButton />
</CardMedia>
</Grid>
</Box>
<div className={slider.root}>
<Typography>Amount to raise</Typography>
<Box>
<Grid container justify='center'>
<Slider
min={500}
max={10000}
value={typeof money === "number" ? money : 0}
onChange={handleSliderChange}
aria-labelledby='input-slider'
/>
<TextField
className={slider.input}
value={money}
onChange={handleInputChange}
InputProps={{
startAdornment: (
<InputAdornment position='start'>$</InputAdornment>
),
}}
helperText={
money < 500 || money > 10000
? "Please enter a value between 500 and 10000"
: ""
}
/>
</Grid>
</Box>
</div>
<Box mt={1} mb={1}>
<Grid container justify='center'>
<TextField
size='small'
inputProps={{
style: { fontSize: 15 },
}}
id='outlined-multiline-static'
multiline
rows={1}
placeholder='Hashtags'
variant='outlined'
value={hashtag}
onChange={handleHashtagChange}
helperText={
arrayOfHashtags.length === 3
? "You reached the maximum amount of hashtags"
: ""
}
/>
<Button color='primary' onClick={newHashtag}>
Create!
</Button>
{arrayOfHashtags.length > 0 ? Hashtags : ""}
</Grid>
</Box>
<Box mt={1} justify='center'>
<Grid container justify='center'>
<TextField
size='small'
inputProps={{
style: { fontSize: 15 },
}}
id='outlined-multiline-static'
multiline
rows={5}
placeholder='Description'
variant='outlined'
value={description}
onChange={handleDescriptionChange}
helperText={
// only displays helper text if button has been clicked and fields haven't been filled
open
? !noDescription || petition
? ""
: "Can't be an empty field"
: ""
}
/>
</Grid>
</Box>
<Box mt={1}>
<Grid container justify='center'>
<Button onClick={clearField}>
<CreateIcon />
Create Petition!
</Button>
{open && petition && (
<Snackbar open={open} autoHideDuration={2000} onClose={handleClose}>
<Alert severity='success'>
You have successfully create a petition!
</Alert>
</Snackbar>
)}
{open && !petition && (
<Snackbar open={open} autoHideDuration={2000} onClose={handleClose}>
<Alert severity='error'>You're missing one or more fields</Alert>
</Snackbar>
)}
</Grid>
</Box>
</StyledCard>
);
};
export default connect(mapStateToProps)(PetitionForm);
This is the error I'm getting, someone mentioned something about the scope and I think that shouldn't matter when I'm trying to assign a value to a variable as far I as know
Line 109:19: 'imageArray' is not defined no-undef
Line 113:11: 'imageUrlArr' is not defined no-undef
Line 123:24: 'imageUrlArr' is not defined no-undef
Search for the keywords to learn more about each error.

Textfields not tracking text

I have a forum setup using react-redux and material ui. All of the text fields seem to not track the change of state this is suppose to track..
So when a user attempts to type in the text field nothing happens.
onChange={e =>
onTextFieldChange("workoutCompleted", e.target.value)
}
here is the rest of the forum code im sure its a real simple fix here.
import React from "react"
import { connect } from "react-redux"
import TextField from "#material-ui/core/TextField"
import Button from "#material-ui/core/Button"
import CustomSnackBar from "../Components/customSnackBar"
import { withStyles } from "#material-ui/core/styles"
import { NEW_WORKOUT_FORM_UPDATED } from "../constants"
import { createNewWorkout } from "../action-creators/events"
const styles = theme => ({
input: {
width: "50%",
marginLeft: 16,
marginTop: 16,
marginBottom: 10,
color: "red"
},
button: {
color: "secondary"
}
})
class NewWorkout extends React.Component {
componentDidMount() {
console.log("componentDidMount!!!")
}
render() {
console.log("RENDER!!!")
const { workout, duration, eventDateTime } = this.props.event
const {
isError,
isSaving,
errorMsg,
classes,
onTextFieldChange,
createWorkout,
history
} = this.props
return (
<div style={{ paddingTop: 56 }}>
<form autoComplete="off" onSubmit={createWorkout(history)}>
<TextField
label="Workout"
value={workout}
onChange={e =>
onTextFieldChange("workoutCompleted", e.target.value)
}
margin="normal"
required
className={classes.input}
/>
<TextField
label="Duration"
value={duration}
onChange={e => onTextFieldChange("Duration", e.target.value)}
margin="normal"
required
className={classes.input}
multiline
/>
<TextField
label="Date"
value={eventDateTime}
clickable="false"
margin="normal"
required
className={classes.input}
/>
<div style={{ paddingTop: 50 }}>
<Button
className={classes.button}
color="red400"
variant="contained"
type="submit"
aria-label="add"
>
SUBMIT
</Button>
</div>
</form>
{isError && <CustomSnackBar message={errorMsg} snackType="error" />}
{isSaving && <CustomSnackBar message="Saving..." snackType="info" />}
</div>
)
}
}
const mapStateToProps = state => {
console.log("What is state?", state)
return {
event: state.newWorkout.data,
isError: state.newWorkout.isError,
isSaving: state.newWorkout.isSaving,
errorMsg: state.newWorkout.errorMsg
}
}
const mapActionsToProps = dispatch => {
return {
onTextFieldChange: (key, value) => {
dispatch({ type: NEW_WORKOUT_FORM_UPDATED, payload: { [key]: value } })
},
createWorkout: history => e => {
e.preventDefault()
dispatch(createNewWorkout(history))
}
}
}
const connector = connect(
mapStateToProps,
mapActionsToProps
)
export default connector(withStyles(styles)(NewWorkout))
onChange={e =>
onTextFieldChange("workoutCompleted", e.target.value)
}
should be
onChange= e => {
onTextFieldChange("workoutCompleted", e.target.value)
}
no?
Thanks for the help #UXDart
you are changing "workoutCompleted" but then checking the property
inside event "workout"... anyway IMO use state for that, and send to
redux when submit the form

Categories