Dynamically add component in react with hooks - reactjs

i have 3 components: Form (parent), Picklist and ParagraphBox (children); based on the select of the picklist, i render ParagraphBox and also a "+" button. What i would like to achieve is on the click of the plus button, render another ParagraphBox, just under the first. I would also like the remove functionality.
My ParagraphBox component has a title and a content, and i want to give the adding a progressive number:
e.g Paragraph 1
Content: ....
Paragraph 2
Content: ....
And so on
Here's my ParagraphBox component:
import React, { useState, useEffect } from 'react';
export default function ParagraphBox(props) {
const [paragrafo, setParagrafo] = useState({})
useEffect(() => {
console.log('paragrafo ', paragrafo)
props.onChange(paragrafo)
}, [paragrafo])
const onChange = (e) => {
const titolo = e.target.name
const contenuto = e.target.value
setParagrafo({
...paragrafo,
[titolo]: contenuto
})
}
return (
<div className = "paragraph-box">
<label>
{props.labelInputBox}
<div>
<input type="text" name="titolo" value={paragrafo.titolo || ''} onChange={onChange}/>
</div>
{props.labelTextArea}
<div>
<textarea id="subject" name="contenuto" placeholder="Inserisci contenuto.." style={{height: "45x", width: "400px"}} value={paragrafo.contenuto || ''} onChange={onChange} />
</div>
</label>
</div>
)
}
Here is my Form component:
import React, { useState, useEffect, useRef } from 'react';
import './Form.css'
import createDocument from '../pdfTool';
import finalita from '../icons/finalita.PNG';
import Picklist from './Picklist.js';
import ParagraphBox from './ParagraphBox';
export default function Form() {
const [flagImg, setFlagImg] = useState(false)
const [flagPar, setFlagPar] = useState(false)
const [paragArray, setParagArray] = useState([
{titolo: '', contenuto: ''}
])
const handleChange = (e) => {
console.log('e ', e)
console.log('e.titolo PARENT ', e.titolo)
console.log('e.contenuto PARENT ', e.contenuto)
setParagArray({
...paragArray,
[e.titolo]: e.contenuto
})
}
useEffect(() => {
console.log('rendering useEffect')
console.log('flagPar: ', flagPar)
console.log('flagImg: ', flagImg)
console.log('paragArray ', paragArray)
}, [flagPar, flagImg, paragArray])
const handleSubmit = (evt) => {
evt.preventDefault(); //usato per evitrare il refresh del browser
}
const addParag = (parag) => {
console.log('paragArray PARENT ', paragArray)
}
const onSelect = (selectedValue) => {
console.log('valore selezionato nella picklist: ' + selectedValue)
if(selectedValue === 'Layout 2') {
setFlagImg(true)
setFlagPar(true)
}
}
return(
<div>
<Picklist onSelect={onSelect} label="Seleziona un layout di contratto: " pickVals={["Seleziona...", "Layout 1", "Layout 2", "Layout 3"]}/>
{flagImg ? (
<form onSubmit={handleSubmit}>
<Picklist onSelect={onSelect} label="Seleziona Immagine: " pickVals={["Seleziona...", "Immagine 1", "Immagine 2", "Immagine 3"]} />
</form>
) : null}
{flagPar ? (
<div>
<ParagraphBox labelInputBox="Paragfrafo 1" labelTextArea="Contenuto Paragrafo" onChange={handleChange}/>
<div id = "add-paragraph">
<button type="button" onClick={addParag}>+</button>
<input type="submit" value="Submit" />
</div>
</div>
) : null}
</div>
)
Thanks in advance for your time

I know this is old...but I just faced the same issue, so here it goes: JSX is just syntactic sugar for regular JavaScript. Therefore you can just create the component manually and make it available as part of your hook, i.e.:
custom hook:
import React, { useState } from 'react';
import Advise from '../../components/Advise/Advise';
const useAdvise = () => {
const [ showAdvise, setShowAdvise ] = useState(false)
const [ adviseMsg, setAdviseMsg ] = useState('')
const [ loading, setLoading ] = useState(false)
const hideAdvise = () => {
setShowAdvise(false)
}
const adviseComponent = React.createElement(Advise, {show:showAdvise, showSpinner:loading, hideAdvise:hideAdvise, children:adviseMsg})
return {
adviseComponent,
setShowAdvise,
setAdviseMsg,
setLoading
}
};
export default useAdvise;
component where I want it:
import useAdvise from '../hooks/useAdvise/useAdvise'
const Page = () => {
const {adviseComponent, setShowAdvise, setAdviseMsg, setLoading} = useAdvise()
return(
<div>
{adviseComponent}
</div>
)
}
hope it helps (cheers from Br as well)

Related

How to run a useffect when something happens in another component in react?

EssayManager.js
import React from "react";
import { useState, useEffect } from "react";
import CollegeEssays from "./CollegeEssays";
import { getDocs, collection } from "firebase/firestore";
import { db } from "../Firebase";
import TextEditor from "./TextEditor";
function EssayManager() {
const [college, setCollege] = useState();
const [collegeList, setColleges] = useState([]);
const [essayList, setEssays] = useState([]);
useEffect(() => {
if (sessionStorage.getItem("user") == null) {
sessionStorage.setItem(
"previousPage",
"http://localhost:3000/essaymanager"
);
window.location = "http://localhost:3000/signIn";
}
});
useEffect(() => {
async function loadUser() {
const colleges = await getDocs(
collection(db, JSON.parse(sessionStorage.getItem("user")).email)
);
colleges.forEach((doc) => {
if (doc.id != "user") {
setColleges((current) => [
...current,
<div
className="collegedivclass"
id={doc.id}
key={doc.id}
onClick={() => changeCollege(doc.id)}
>
<CollegeEssays name={doc.id} />
</div>,
]);
}
});
}
loadUser();
}, []);
useEffect(() => {
}, [])
useEffect(() => {
async function loadEssays() {
setEssays([]);
const essays = await getDocs(
collection(
db,
JSON.parse(sessionStorage.getItem("user")).email,
`${college}`,
"essays"
)
);
essays.forEach((doc) => {
if (doc.id != "exists") {
setEssays((current) => [
...current,
<li key={doc.id}>
<TextEditor
prompt={doc.id}
text={doc.data().text}
count={doc.data().count}
countType={doc.data().countType}
college={college}
/>
</li>,
]);
}
});
}
if (college != undefined) {
loadEssays();
}
}, [college]);
async function changeCollege(id) {
setCollege(id);
}
return (
<>
{collegeList.length > 0 ? (
<div id="page">
<ul id="listul">{collegeList}</ul>
<h6>*New essays will only show up when the college is clicked again</h6>
<h1 id="collegename">{college}</h1>
<ul>{essayList}</ul>
</div>
) : (
<h1>No Colleges in List</h1>
)}
</>
);
}
export default EssayManager;
CollegeEssays.js
import React from "react";
import AddEssay from "./AddEssay";
import "../UsersEssays.css";
function CollegeEssays(props) {
return (
<>
<div className="useressay">
<h5 id={props.name}>{props.name}</h5>
<AddEssay id="addessaybutton" college={props.name} />
</div>
</>
);
}
export default CollegeEssays;
AddEssay.js
import React from "react";
import Popup from "reactjs-popup";
import { useState } from "react";
import { db } from "../Firebase";
import { setDoc, doc } from "firebase/firestore";
import "../UsersEssays.css";
function AddEssay(props) {
const [prompt, setPrompt] = useState("");
const [countType, setCountType] = useState();
const [count, setCount] = useState("");
const college = props.college;
function handlePrompt(e) {
setPrompt(e.target.value);
}
function handleCountType(e) {
if (
document.getElementById("wordCount").checked ||
document.getElementById("charCount").checked
) {
document.getElementById("countLabel").hidden = false;
document.getElementById("count").hidden = false;
} else {
document.getElementById("countLabel").hidden = true;
document.getElementById("count").hidden = true;
}
setCountType(e.target.value);
}
function handleCount(e) {
setCount(e.target.value);
}
async function handleSubmit(e) {
e.preventDefault();
try {
await setDoc(
doc(
db,
JSON.parse(sessionStorage.getItem("user")).email,
college,
"essays",
prompt
),
{
text: "",
countType: countType,
count: count,
}
);
} catch (e) {
console.error("Error adding document: ", e);
}
}
return (
<Popup id="popup" trigger={<button>Add</button>} modal>
{(close) => (
<div id="form">
<form>
<h3>Add an essay for {college}</h3>
<label>Prompt:</label>
<input type="text" value={prompt} onChange={handlePrompt} />
<br />
<fieldset onChange={handleCountType}>
<legend>Count Type</legend>
<input
type="radio"
name="countType"
id="wordCount"
value="Word Count"
/>
<label>Word Count</label>
<br />
<input
type="radio"
name="countType"
id="charCount"
value="Character Count"
/>
<label>Character Count</label>
<br />
<input
type="radio"
name="countType"
id="noCount"
value="No Count"
/>
<label>No Count</label>
<br />
</fieldset>
<label id="countLabel" hidden>
Count:
</label>
<input
type="text"
value={count}
id="count"
onChange={handleCount}
hidden
/>
<br />
<input
type="submit"
value="Add"
onClick={(event) => {
handleSubmit(event);
close();
}}
/>
</form>
</div>
)}
</Popup>
);
}
export default AddEssay;
I'm using firestore through the google cloud to save the essay information. The essays are pulled from the database and rendered only when the user selects a new college, changing the college variable and triggereing the useeffect. This means that if a user adds a new essay through the essay form, that new essay won't show up until the user selects the college again.
I'm hoping to find a way to cause the useeffect or at least the function in EssayManager.js to trigger when the user submits the form in AddEssay.js.
You can wrap your EssayComponent with a useContext provider. In this provider, you create the essay state. In this way, any component within this context can access the essay state.
https://beta.reactjs.org/reference/react/useContext

refactor debounce function to useDeferredValue hook (React)

In my application I use Redux to manage its state.
The task is when user types query in search panel application dispatches an action with payload and then request goes to API. I want to delay dispatch of an action, so when user types query the request only sends after user stops typing.
I implemented it with debounce function but kinda want to refactor in with useDeferredValue.
And that's when I found it difficult to implement this functional.
import { useState, useMemo } from 'react';
import { FormRow, FormRowSelect } from '.';
import Wrapper from '../assets/wrappers/SearchContainer';
import { useSelector, useDispatch } from 'react-redux';
import { handleChange, clearFilters } from '../features/allJobs/allJobsSlice';
export default function SearchContainer() {
const { isLoading, search, searchStatus, searchType, sort, sortOptions } =
useSelector((store) => store.allJobs);
const { jobTypeOptions, statusOptions } = useSelector((store) => store.job);
const dispatch = useDispatch();
const [localSearch, setLocalSearch] = useState('');
function onHandleSearch(e) {
dispatch(handleChange({ name: e.target.name, value: e.target.value }));
}
function omHandleSubmit(e) {
e.preventDefault();
dispatch(clearFilters());
}
const debounce = () => {
let timeoutID;
return (e) => {
setLocalSearch(e.target.value);
clearTimeout(timeoutID);
timeoutID = setTimeout(() => {
dispatch(handleChange({ name: e.target.name, value: e.target.value }));
}, 1000);
};
};
const optimizedDebounce = useMemo(() => debounce(), []);
return (
<Wrapper>
<form className='form'>
<h4>search form</h4>
<div className='form-center'>
<FormRow
type='text'
name='search'
value={localSearch}
handleChange={optimizedDebounce}
/>
<FormRowSelect
labelText='status'
name='searchStatus'
value={searchStatus}
handleChange={onHandleSearch}
list={['all', ...statusOptions]}
/>
<FormRowSelect
labelText='type'
name='searchType'
value={searchType}
handleChange={onHandleSearch}
list={['all', ...jobTypeOptions]}
/>
<FormRowSelect
name='sort'
value={sort}
handleChange={onHandleSearch}
list={sortOptions}
/>
<button
className='btn btn-block btn-danger'
disabled={isLoading}
onClick={omHandleSubmit}
>
clear filters
</button>
</div>
</form>
</Wrapper>
);
}
From the react website this is how it is done:
function App() {
const [text, setText] = useState("hello");
const deferredText = useDeferredValue(text, { timeoutMs: 2000 });
return (
<div className="App">
{/* Continue to give the current text to the input */}
<input value={text} onChange={handleChange} />
...
{/* But the list of results is allowed to be "late" in case it is not load yet */}
<MySlowList text={deferredText} />
</div>
);
}
so in your case this might be this:
import { useDeferredValue } from 'react';
export default function SearchContainer() {
const [localSearch, setLocalSearch] = useState('');
const deferredValue = useDeferredValue(localSearch, { timeoutMs: 1000 });
...
return (
...
<FormRow
type='text'
name='search'
value={localSearch}
handleChange={e => setLocalSearch(e.target.value)}
/>
);
}

How to handle multiple select options submittion in react js?

I want to submit a form into mongoDB using nodejs API & reactJs. With the exception of the multiple select option, everything is operating as it should be.
Being new to react, I have no idea how to handle the multi select option's onChange method.
Here is what I've tried:
import React, { useState, useRef } from "react";
import { useForm } from "react-hook-form";
import { v4 as uuidv4 } from 'uuid';
import axios from "axios";
import Select from 'react-select';
export default function EventForm(props) {
const {
register,
handleSubmit,
reset,
formState: { errors },
} = useForm();
const form = useRef();
const [loading, setLoading] = useState(false);
const [info, setInfo] = useState("");
const [analysis, setAnalysis] = useState("Undefined");
const [relatedEvent, setRelatedEvent] = useState([]);
const handleInfoChange = (e) => {
setInfo(e.target.value)
}
const handleAnalysisChange = (e) => {
setAnalysis(e.target.value)
}
const handleRelatedEvents = (e) => {
setRelatedEvent(e.target.value)
}
const relatedEventsData = props.data.map(opt => ({ label: opt.info, value: opt._id }));
const onSubmit = async () => {
setLoading(true);
const MySwal = withReactContent(Swal);
const eventData = {
UUID: uuidv4(),
info: info,
analysis: analysis,
relatedEvent: relatedEvent,
}
axios
.post(`${process.env.REACT_APP_PROXY}/api/events`, eventData)
.then((res) => {
console.log(res);
setLoading(false);
MySwal.fire(
"Success!",
"A new event has been saved successfully",
"success"
);
})
.catch((error) => {
console.log(error);
});
};
return (
<div className="panel-body">
<Form
ref={form}
onSubmit={handleSubmit(onSubmit)}
className="form-horizontal"
>
<div className="row">
<div className="col-lg-6">
<div className="mb-3">
<Form.Label>Info</Form.Label>
<Form.Control
type="text"
placeholder="Enter info..."
{...register("info", { required: true })}
value={info}
onChange={handleInfoChange}
/>
{errors.info && (
<ul className="parsley-errors-list filled" id="parsley-id-7" aria-hidden="false">
<li className="parsley-required">This value is required.</li>
</ul>
)}
</div>
</div>
<div className="col-lg-6">
<div className="mb-3">
<Form.Label>Related events</Form.Label>
<Select
options={relatedEventsData}
value={relatedEvent}
isMulti
onChange={handleRelatedEvents}
/>
</div>
</div>
<div className="col-lg-12">
<Button variant="primary" type="submit">
{loading ? "Saving..." : "Save"}
</Button>
</div>
</div>
</Form>
</div>
);
}
Could you please guide me how to make it work!
Thank you
you can make use of Select onChange event handler which passes the selected options as an array as argument ..
from that you can map over it to get the values as required
something as below:
const handleChange = (opts) => {
const selectedValues = opts.map((opt) => opt.value);
setSelectedValues(selectedValues);
};
Please check the working sample for better clarity 😉 -

How to manage radio button state with React Typescript?

I am implementing a simple signup page with React Typescript.
I'm trying to set the gender with the radio button, save it in the state, and send it to the server, but the toggle doesn't work.
What should I do?
//RegisterPage.tsx
const [radioState, setradioState] = useState(null);
const [toggle, settoggle] = useState<boolean>(false);
const onRadioChange = (e: any) => {
setradioState(e);
console.log(radioState);
};
const genderOps: ops[] = [
{ view: "man", value: "man" },
{ view: "woman", value: "woman" },
];
<div>
{genderOps.map(({ title, gender }: any) => {
return (
<>
<input
type="radio"
value={gender}
name={gender}
checked={gender === radioState}
onChange={(e) => onRadioChange(gender)}
/>
{title}
</>
);
})}
</div>
You should do some changes on your code, here what you should do:
import React, { EventHandler, useState } from "react";
import "./styles.css";
export default function App() {
const [radioState, setradioState] = useState("");
const [toggle, settoggle] = useState<boolean>(false);
const onRadioChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setradioState(e.currentTarget.value);
};
const genderOps = [
{ view: "man", value: "man" },
{ view: "woman", value: "woman" }
];
return (
<div className="App">
<div>
{genderOps.map(({ view: title, value: gender }: any) => {
return (
<>
<input
type="radio"
value={gender}
name={gender}
checked={gender === radioState}
onChange={(e) => onRadioChange(e)}
/>
{title}
</>
);
})}
</div>{" "}
</div>
);
}

A method triggers a console.log from another component

When I click on bet now the function triggers a console.log from another component. betNow should group all the inputs from stake in one common array but when I click on it it renders the console log from stake and includes all the values that I typed into one array. Everything works but not as I wish. The parent component should display the common array with all the values. I do not understand why it is happening.Could anyone explain me why is reacting like that? Thanks in advance
Parent Component
import React, { useState } from 'react';
import Button from '#material-ui/core/Button';
import FilterMenu from "./selectButton";
import FetchRandomBet from "./fetchRandomBets";
function Betslip() {
const data = [
{
value: 0,
label: "No Filter"
},
{
value: 1,
label: "Less than two"
},
{
value: 2,
label: "More than two"
},
]
const [selectedValue, setSelectedValue] = useState(0);
const [allStakes, setAllStakes] = useState([]);
const handleChange = obj => {
setSelectedValue(obj.value);
}
const betNow = () => {
const stakes = localStorage.getItem("stakes");
const jsnStake = JSON.parse(stakes) || [];
setAllStakes([...allStakes, jsnStake]);
}
return (
<div className="betslip">
<div className="betslip-top">
<h1 className="text">BETSLIP</h1>
<p className="text-two">BET WITH US!</p>
<div>
<FilterMenu
optionsProp={data}
valueProp={selectedValue}
onChangeProp={handleChange}
/>
</div>
</div>
<div>
<FetchRandomBet
valueProp={selectedValue}
/>
</div>
<Button
onClick={betNow}
className="betnow"
variant="contained"
>
Bet Now!
</Button>
</div>
);
}
export default Betslip;
Child Component
import React, { useState, useEffect } from 'react';
function Stake() {
const [stakes, setStakes] = useState([]);
const addStake = (e) => {
e.preventDefault();
const newStake = e.target.stake.value;
setStakes([newStake]);
};
useEffect(() => {
const json = JSON.stringify(stakes);
localStorage.setItem("stakes", json);
}, [stakes]);
console.log(stakes)
return (
<div>
<form onSubmit={addStake}>
<input
style={{
marginLeft: "40px",
width: "50px"
}}
type="text"
name="stake"
required
/>
</form>
</div>
);
}
export default Stake;
You have this console.log in you function that will run every time the component is rendered, since it´s outside of any function:

Resources