I'm trying to done a form in react that have subcomponents for uploaded images (to do a preview and do it more beautyfull) but the thing is that I can't access to the useState of the child where is the image that I need to send to de backend.
Here is the code of the subcomponent and in the useState I need to acces throught the parent to the image:
import React, { useState, Fragment } from "react";
import {
Layout,
Container,
BoxUpload,
ContainerUploadImage,
TextUploadImage,
LabelUploadImage,
ImagePreview,
} from "./ImageUploadElements";
import UploadPhoto from "../../../images/upload.svg";
import CloseIcon from "../../../images/close.svg";
const ImageUpload = ({text}) => {
const [image, setImage] = useState("");
const [isUploaded, setIsUploaded] = useState(false);
const handleImageChange = (e) => {
if (e.target.files && e.target.files[0]) {
let reader = new FileReader();
reader.onload = (e) => {
setImage(e.target.result);
setIsUploaded(true);
};
reader.readAsDataURL(e.target.files[0]);
}
};
return (
<Layout>
<Container>
<h2>{text}</h2>
<BoxUpload>
<div className="image-upload">
{isUploaded ? (
<ImagePreview>
<img
className="close-icon"
src={CloseIcon}
alt="CloseIcon"
onClick={() => {
setIsUploaded(false);
setImage(null);
}}
/>
<img
src={image}
className="uploaded-image"
draggable={false}
alt="progress-uploaded"
/>
</ImagePreview>
) : (
<Fragment>
<LabelUploadImage htmlFor="upload-input">
<ContainerUploadImage
src={UploadPhoto}
alt="Upload Icon"
draggable={false}
/>
<TextUploadImage>Click to upload image</TextUploadImage>
</LabelUploadImage>
<input
type="file"
name="upload-input"
accept=".jpg,.jpeg,.gif,.png,.mov,.mp4"
onChange={handleImageChange}
/>
</Fragment>
)}
</div>
</BoxUpload>
</Container>
</Layout>
);
};
export default ImageUpload;
And here in that upload form component is where I need to get acces to this image to send it with axios to backend:
import React, { Fragment, useState } from "react";
import {
Container,
FormWrap,
FormContent,
Form,
FormH1,
FormLabel,
FormInput,
FormButton,
FormErrorWrap,
FormError,
FormErrorText,
PhotoWrap
} from "./UploadElements";
import ImageUpload from "../ImageUpload";
import { frontPhotoText, sidePhotoText, backPhotoText } from "./Data";
const Upload = () => {
const [weight, setWeight] = useState("");
const [uploadErrors, setUploadErrors] = useState([{}]);
const upload = (e) => {
e.preventDefault();
// Here will go the axios peticiĆ³n with the wight and the three images uploaded.
}
return (
<Fragment>
<Container>
<FormWrap>
<FormContent>
<Form onSubmit={upload}>
<FormH1>Upload New Progress</FormH1>
<FormLabel htmlFor="weight">Weight</FormLabel>
<FormInput
onChange={(e) => setWeight(e.target.value)}
type="number"
value={weight}
id="weight"
required
/>
<PhotoWrap>
<ImageUpload {...frontPhotoText}/>
<ImageUpload {...sidePhotoText}/>
<ImageUpload {...backPhotoText}/>
</PhotoWrap>
<FormErrorWrap>
{uploadErrors ? (
uploadErrors.map((err, index) => (
<FormError key={index}>
<FormErrorText>{err.msg}</FormErrorText>
</FormError>
))
) : (
<Fragment></Fragment>
)}
</FormErrorWrap>
<FormButton>Upload</FormButton>
</Form>
</FormContent>
</FormWrap>
</Container>
</Fragment>
);
};
export default Upload;
But I don't know how can I get this images throught the parent, if anyone can help I'll be very gratefull, thanks!!!
You can use a combination of forwardRef and useImperativeHandle to expose out a function from the child component that a parent component can invoke.
Child - Import and decorate the child component with forwardRef and use the useImperativeHandle to expose a getImage function that returns the current image state.
import React, { useState, Fragment, forwardRef } from "react";
...
const ImageUpload = forwardRef(({text}, ref) => {
const [image, setImage] = useState("");
const [isUploaded, setIsUploaded] = useState(false);
useImperativeHandle(ref, () => ({
getImage: () => image,
}));
const handleImageChange = (e) => {
...
};
return (
...
);
});
Parent - Create a React ref to pass to ImageUpload and in the callback access the current ref value and invoke the function.
import React, { Fragment, useState, useRef } from "react";
...
const Upload = () => {
const [weight, setWeight] = useState("");
const imageUploadFrontRef = useRef();
const imageUploadSideRef = useRef();
const imageUploadBackRef = useRef();
const [uploadErrors, setUploadErrors] = useState([{}]);
const upload = (e) => {
e.preventDefault();
const imageFront = imageUploadFrontRef.current.getImage();
const imageSide = imageUploadSideRef.current.getImage();
const imageBack = imageUploadBackRef.current.getImage();
// do with the images what you need.
}
return (
<Fragment>
<Container>
<FormWrap>
<FormContent>
<Form onSubmit={upload}>
...
<PhotoWrap>
<ImageUpload ref={imageUploadFrontRef} {...frontPhotoText} />
<ImageUpload ref={imageUploadSideRef} {...sidePhotoText} />
<ImageUpload ref={imageUploadBackRef} {...backPhotoText} />
</PhotoWrap>
...
</Form>
</FormContent>
</FormWrap>
</Container>
</Fragment>
);
};
Related
So I have a context.
import React from "react";
const CartContext = React.createContext({
numberOfMeals: 0,
meals: [],
});
export default CartContext;
I then import this into my App.JS
import React from "react";
import MealsBase from "./Components/Meals/MealsBase";
import CartContext from "./Context/CartContext";
function App() {
return (
<CartContext.Provider value={{ numberOfMeals: 0, meals: [] }}>
<MealsBase/>
</CartContext.Provider>
);
}
export default App;
I use this context within two components.
Component 1 :
import React, {useContext} from "react";
import "./CartIcon.css";
import CartContext from "../../Context/CartContext";
const CartIcon = () => {
const cartContext = useContext(CartContext);
return (
<React.Fragment>
<button className="border">
<h3>Your Cart : {cartContext.numberOfMeals}</h3>
</button>
</React.Fragment>
);
};
export default CartIcon;
Component 2 :
import React,{ useState, useContext } from "react";
import CartContext from "../../Context/CartContext";
const MealForm = () => {
const [amount, setAmount] = useState(0);
const mealContext = useContext(CartContext);
const amountHandler = (event) => {
setAmount(event.target.valueAsNumber);
};
const formHandler = (event) => {
mealContext.numberOfMeals = mealContext.numberOfMeals + amount;
console.log(mealContext.numberOfMeals);
event.preventDefault();
}
return(
<React.Fragment>
<form onSubmit={formHandler}>
<label>Amount</label>
<input type="number" step={1} min={-1} max={50} value={amount} onChange={amountHandler}/>
<button type="submit"> + Add </button>
</form>
</React.Fragment>
);
};
export default MealForm
The context seems to update in component two but not in component one.
I have an understanding that if useContexts values change then it causes a component to re-render. So I am struggling to understand why the CartIcon.js file is not being re-rendered.
You aren't really updating the value the React way, you are mutating it. Move the context value into state and provide that out.
const CartContext = React.createContext({
numberOfMeals: 0,
setNumberOfMeals: () => {},
meals: [],
setMeals: () => {},
});
...
function App() {
const [numberOfMeals, setNumberOfMeals] = useState(0);
const [meals, setMeals] = useState([]);
return (
<CartContext.Provider value={{ numberOfMeals, setNumberOfMeals, meals, setMeals }}>
<MealsBase/>
</CartContext.Provider>
);
}
...
const MealForm = () => {
const [amount, setAmount] = useState(0);
const { setNumberOfMeals } = useContext(CartContext);
const amountHandler = (event) => {
setAmount(event.target.valueAsNumber);
};
const formHandler = (event) => {
event.preventDefault();
setNumberOfMeals(count => count + amount);
}
return(
<React.Fragment>
<form onSubmit={formHandler}>
<label>Amount</label>
<input type="number" step={1} min={-1} max={50} value={amount} onChange={amountHandler}/>
<button type="submit"> + Add </button>
</form>
</React.Fragment>
);
};
I'm a bit lost here. I've done this a bunch of time and have never had this issue before. I'm passing a boolean state to a modal component. I followed the code from the parent and it is set properly but as soon as it gets to the modal it returns as undefined.
Here is the parent:
import React, { useEffect, Fragment, useState } from 'react'
import './styles.css'
import LandingPageModal from './LandingPageModal'
import { testImages } from './testData'
const LandingPage = () => {
const [images, setImages] = useState([])
const [renderImages, setRenderImages] = useState(false)
const [showModal, setShowModal] = useState(false)
const [isLoaded, setIsLoaded] = useState(false)
useEffect(() => {
setImages(testImages)
setShowModal(true)
setIsLoaded(true)
}, [])
useEffect(() => {
if (images && images.length > 0) {
setRenderImages(true)
}
}, [images])
const FeaturedUsers = () => {
return (
renderImages ?
<Fragment>
<div className='grid'>
{images.map((image) => (
<img src={`/images/${image.src}`} alt={image.caption} />
))}
</div>
</Fragment> : ''
)
}
return(
isLoaded ?
<Fragment>
<FeaturedUsers />
<LandingPageModal show={showModal} />
</Fragment> : ''
)
}
export default LandingPage
and here is the modal:
import React, { useState, useEffect } from 'react'
import ReactModal from 'react-modal'
import './styles.css'
const LandingPageModal = ({ showModal }) => {
const [isModalOpen, setIsModalOpen] = useState(showModal)
console.log('Is Show: ' + showModal)
return (
<ReactModal
isOpen={isModalOpen}
>
<div className='main-wrapper'>
<div className='text'>
<p>
<strong>Welcome</strong>
<br />
<br />
Please sign in or sign up
</p>
</div>
</div>
</ReactModal>
)
}
export default LandingPageModal
In the LandingPage component, you accidentally renamed showModal to show.
I have a two Input form and a Paragraph, when I try to change the value of input the paragraph get updated, once the paragraph is updated I am trying to edit the paragraph with the help of drafts library, but once I update the paragraph and save it, it doesn't update the paragraph.
Please anyone Help me out to solve the problem
Codesandbox Link : Code
Context API
import React, { useState, createContext } from "react";
export const Contx = createContext();
export const ConProvider = ({ children }) => {
const [customerName, setCustomerName] = useState("");
const [amount, setAmount] = useState("");
const defaultValue = `<p>Hello ${
customerName === "" ? "User" : customerName
},</p>
<p>Please Click on the link below to pay the order - <strong>${amount}</strong> . </p>
<p>Click hear to pay</p>
<br/>
<p>Thanks and Regards</p>
<p>testUser</p>`;
const [draftValue, setDraftValue] = useState(defaultValue);
return (
<Contx.Provider
value={{
defaultValue,
setCustomerName,
setAmount,
customerName,
amount,
setDraftValue,
draftValue
}}
>
{children}
</Contx.Provider>
);
};
homePage
import React, { useContext, useState } from "react";
import ReactDOM from "react-dom";
import { ConProvider, Contx } from "../ContextApi";
import Data from "./Component/Data/Data";
import NewDraft from "./Component/Data/NewDraft";
import Modal from "./Component/Data/Modal";
import "./styles.css";
function App() {
const { defaultValue, setDraftValue, draftValue } = useContext(Contx);
// console.log("defaultValue", defaultValue);
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
return (
<div className="App">
<Data />
<Modal handleClose={handleClose} show={show}>
<NewDraft
prsFunc={setDraftValue}
handleClose={handleClose}
defaultValueEmpty={false}
defaultValue={defaultValue}
/>
</Modal>
<div
className="templateStyle p-2"
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{
__html: draftValue && draftValue
}}
/>
<button onClick={handleShow}>Edit</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(
<ConProvider>
<App />
</ConProvider>,
rootElement
);
Input Form
import React, { useContext } from "react";
import { Contx } from "../../../ContextApi";
export default function Data() {
const {
setCustomerName,
setDraftValue,
defaultValue,
setAmount,
customerName,
amount
} = useContext(Contx);
React.useEffect(() => {
setDraftValue(defaultValue);
});
// console.log("fffffff", customerName, amount);
return (
<div>
<input
type="text"
value={customerName}
name="customerName"
placeholder="Enter Customer name"
onChange={(e) => {
setCustomerName(e.target.value);
}}
/>
<input
type="number"
placeholder="Enter Amount"
value={amount}
onChange={(e) => setAmount(e.target.value)}
/>
</div>
);
}
DraftJS
import React, { useState } from "react";
import { Editor } from "react-draft-wysiwyg";
import {
EditorState,
convertToRaw,
ContentState,
convertFromHTML
} from "draft-js";
import "react-draft-wysiwyg/dist/react-draft-wysiwyg.css";
import draftToHtml from "draftjs-to-html";
import "./Data.css";
export default function NewDraft({
handleClose,
defaultValue,
defaultValueEmpty,
prsFunc
}) {
const initialState = defaultValueEmpty
? () => EditorState.createEmpty()
: EditorState.createWithContent(
ContentState.createFromBlockArray(convertFromHTML(defaultValue))
);
const [editorState, setEditorState] = useState(initialState);
const onChange = (value) => {
setEditorState(value);
};
const saveData = () => {
prsFunc(draftToHtml(convertToRaw(editorState.getCurrentContent())));
handleClose();
};
// console.log(draftToHtml(convertToRaw(editorState.getCurrentContent())));
return (
<div>
<div style={{ border: "2px solid", padding: "20px" }}>
<Editor
editorState={editorState}
toolbarClassName="toolbarClassName"
wrapperClassName="wrapperClassName"
editorClassName="editorClassName"
onEditorStateChange={(value) => onChange(value)}
/>
<button variant="secondary" onClick={saveData}>
Save
</button>
</div>
</div>
);
}
The problem you are facing is caused by this line in Data component. Every time the component is updated, the draftValue is set to the defaultValue.
React.useEffect(() => {
setDraftValue(defaultValue);
});
I'm new to react.
Now trying to make form with react hooks, and I want to render Cloud component only when press submit button. But it rendered every onChange called.
I know that onChange re-rendered cause also useState hook.
But have no idea how to render only when press submit button.
My final goal is when write name and press enter, if value is not contained in api, setShake make shake True and if True, put shake-cloud class in Cloud.js.
REACT IS TOO DIFFICULT :(
Thanks for help tho :)
App.js
import React, { useState, useEffect } from "react";
import "./App.css";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faSearch } from "#fortawesome/free-solid-svg-icons";
import "./search.css";
import PageTitle from "./component/PageTitle";
import Cloud from "./component/Cloud";
import Loading from "./component/Loading";
//https://api.color.pizza/v1/
//data.colors[0].name
const App = () => {
const [isLoading, setIsLoading] = useState(false);
const [colorNames, setColorNames] = useState("");
const [search, setSearch] = useState("");
const [query, setQuery] = useState("");
const [cloudHex, setCloudHex] = useState("ivory");
const [shake, setShake] = useState(false);
useEffect(() => {
getColorLists();
}, []);
const getColorLists = async () => {
const res = await fetch(`https://api.color.pizza/v1/`);
const data = await res.json();
await setColorNames(data);
setIsLoading(true);
};
const isColor = () => {
let makeUpper =
query.search(/\s/) == -1
? query.charAt(0).toUpperCase() + query.slice(1)
: query
.split(" ")
.map((i) => i.charAt(0).toUpperCase() + i.slice(1))
.join(" ");
for (let i = 0; i < colorNames.colors.length; i++) {
if (colorNames.colors[i].name == makeUpper) {
setCloudHex(colorNames.colors[i].hex);
return;
} else if (i == colorNames.colors.length - 1) {
return makeShake();
}
}
};
const updateSearch = (e) => {
setSearch(e.target.value);
};
const getSearch = (e) => {
e.preventDefault();
setQuery(search);
isColor();
};
const makeShake = async () => {
await setShake(true)
await setShake(false)
}
return (
<>
{!isLoading ? (
<Loading />
) : (
<div className="App">
<div className="app-wrap">
<PageTitle />
<div className="search-wrap">
<form onSubmit={getSearch} className="search-form">
<input
className="search-bar"
type="text"
value={search}
onChange={updateSearch}
/>
<button type="submit" className="search-button">
<FontAwesomeIcon
icon={faSearch}
className="search"
/>
</button>
</form>
</div>
<Cloud cloudhex={cloudHex} shake={shake} />
</div>
</div>
)}
</>
);
};
export default App;
Cloud.js
import React, {useEffect} from "react";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faCloud } from "#fortawesome/free-solid-svg-icons";
import './cloud.css';
const Cloud = ({cloudhex, shake}) => {
useEffect(() => {
}, [])
console.log(shake)
return (
<div className={`cloud-wrap ${ shake ? "shake-cloud":''}`}>
<span className="cloudhexname">{cloudhex}</span>
<FontAwesomeIcon icon={faCloud} className="cloud" style={{color:`${cloudhex}`}} />
</div>
);
};
export default Cloud;
A good approach in this case is to use useRef() Hook to store our search field value, instead of using useState(). Because useRef() Hook does not force a re-render while useState() does. This approach is known as un-controlled way to use input field.
You basically need to make few modifications in your code which are as follows:
const search = useRef("");
Then remove onChange={updateSearch} and value={search} from input and use a property ref={search}. So that your input looks like below:
<input
className="search-bar"
type="text"
ref={search}
/>
Then in the submit handler, you can get the value of the input field using search.current.value. So your getSearch() would look like
const getSearch = (e) => {
e.preventDefault();
setClicked(true);
setQuery(search.current.value);
isColor();
};
Assuming user has typed an input. If not then you can set a validation before using setQuery() in the getSearch() form submit handler.
if(search.current.value){
setQuery();
}
Note: If you have any other controlled inputs in your project, you can change then to un-controlled inputs using refs and this way re-renders would not happen in your code.
Do it like that
If you want to render cloud component after form submit then put one flag and toggle that, here I take clicked state
import React, { useState, useEffect } from "react";
import "./App.css";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faSearch } from "#fortawesome/free-solid-svg-icons";
import "./search.css";
import PageTitle from "./component/PageTitle";
import Cloud from "./component/Cloud";
import Loading from "./component/Loading";
//https://api.color.pizza/v1/
//data.colors[0].name
const App = () => {
const [isLoading, setIsLoading] = useState(false);
const [colorNames, setColorNames] = useState("");
const [search, setSearch] = useState("");
const [query, setQuery] = useState("");
const [cloudHex, setCloudHex] = useState("ivory");
const [shake, setShake] = useState(false);
const [clicked, setClicked] = useState(false);
useEffect(() => {
getColorLists();
}, []);
const getColorLists = async () => {
const res = await fetch(`https://api.color.pizza/v1/`);
const data = await res.json();
await setColorNames(data);
setIsLoading(true);
};
const isColor = () => {
let makeUpper =
query.search(/\s/) == -1
? query.charAt(0).toUpperCase() + query.slice(1)
: query
.split(" ")
.map((i) => i.charAt(0).toUpperCase() + i.slice(1))
.join(" ");
for (let i = 0; i < colorNames.colors.length; i++) {
if (colorNames.colors[i].name == makeUpper) {
setCloudHex(colorNames.colors[i].hex);
return;
} else if (i == colorNames.colors.length - 1) {
return makeShake();
}
}
};
const updateSearch = (e) => {
setSearch(e.target.value);
};
const getSearch = (e) => {
e.preventDefault();
setClicked(true);
setQuery(search);
isColor();
};
const makeShake = async () => {
await setShake(true)
await setShake(false)
}
return (
<>
{!isLoading ? (
<Loading />
) : (
<div className="App">
<div className="app-wrap">
<PageTitle />
<div className="search-wrap">
<form onSubmit={getSearch} className="search-form">
<input
className="search-bar"
type="text"
value={search}
onChange={updateSearch}
/>
<button type="submit" className="search-button">
<FontAwesomeIcon
icon={faSearch}
className="search"
/>
</button>
</form>
</div>
{clicked && <Cloud cloudhex={cloudHex} shake={shake} />}
</div>
</div>
)}
</>
);
};
export default App;
Hello I got stuck during creating app using hooks
I do not why but my Component does not download a state from my Context Component or maybe my initial state does not update correctly. Does somebody have any idea what's going on?
Context Component:
import React, { createContext, useState } from 'react';
export const WeatherDataContext = createContext();
const WeatherDataContextProvider = (props) => {
const [weather, setWeather] = useState(
{
city: null,
temp: null
}
)
const addWeather = (city, temp) => {
setWeather({
city,
temp
})
}
return (
<WeatherDataContext.Provider value={{weather, addWeather}}>
{props.children}
</WeatherDataContext.Provider>
)
}
export default WeatherDataContextProvider
Form - axios - Component:
import React, {useContext, useState} from 'react';
import { WeatherDataContext } from '../context/WeatherDataContext';
import axios from 'axios'
import {Link} from 'react-router-dom'
const WeatherForm = () => {
const {addWeather} = useContext(WeatherDataContext);
const [value, setValue] = useState('')
const handleChange = (e) => {
e.preventDefault();
axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${value}&appid=${KEY}&units=metric`)
.then(res => {
addWeather(res.data.name, res.data.main.temp)
})
}
return (
<div class='weather-form'>
<form onSubmit={handleChange}>
<input placeholder='City' onChange={(e) => setValue(e.target.value)} value={value} required/>
<Link to='/weather'><button>Search</button></Link>
</form>
</div>
)
}
export default WeatherForm
And final component where I want to use my update state
import React, {useContext, useState} from 'react';
import { WeatherDataContext } from '../context/WeatherDataContext';
const WeatherFront = () => {
const {weather} = useContext(WeatherDataContext)
console.log(weather)
return (
<div class='weather-front'>
<h1>City: {weather.city}, Temperatura: {weather.temp}</h1>
</div>
)
}
export default WeatherFront
Your button is not submitting the form - it navigates away from the page instead.
So handleChange is not being called.
You can call it from buttons onClick instead of forms onSubmit. Be sure to omit e.preventDefault() then, so that parent Link can still navigate.
const WeatherForm = () => {
const { addWeather } = useContext(WeatherDataContext)
const [value, setValue] = useState('')
const handleChange = (e) => {
axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${value}&appid=${KEY}&units=metric`)
.then(res => {
addWeather(res.data.name, res.data.main.temp)
})
}
return (
<div class="weather-form">
<form >
<input
placeholder="City"
onChange={(e) => setValue(e.target.value)}
value={value}
required
/>
<Link to="/weather">
<button onClick={handleChange}>Search</button>
</Link>
</form>
</div>
)
}
Be sure to wrap both pages inside the same context:
<WeatherDataContextProvider>
<Router>
<Switch>
<Route path="/weather">
<WeatherFront></WeatherFront>
</Route>
<Route path="/">
<WeatherForm></WeatherForm>
</Route>
</Switch>
</Router>
</WeatherDataContextProvider>