I've created a simple react component for a form with few input fields using formik. My form is rendered three times on my home page for three different type of users, but I only have one button that is outside the component and on click it should save the data inside the PersonalInformation component. This is how my code looks inside my App.js (ignore the users and data for now):
{users.map((data, i) => { return <PersonalInformation key={i} /> })}
<Button>Submit</Button> //this is the button that needs to save the data inside of PersonalInfo component od click
My question is how I can save the data inside the three forms on click on the button? In end-point on the back end I would like to get an array of three objects, each objects will contain info about each field in the form. I guess what I need is to pass data from PersonalInformation component to onClick()event in Button, but I am not sure how to do that with formik.
if you don't use any state management, context etc i think simplest way is you can pass reference to your save method upper.
import React, {useRef} from "react";
import PersonalInformation from "./PersonalInformation";
import "./styles.css";
export default function App() {
const saveRef = useRef(null)
return (
<div className="App">
<PersonalInformation passSave={(ref) => saveRef.current = ref}/>
<button onClick={() => saveRef.current()}> save </button>
</div>
);
}
//---------------------------------------------------------
import React, { useCallback, useEffect, useRef } from "react";
const PersonalInformation = ({passSave}) => {
const formInput = useRef(null);
const save = useCallback(() => {
console.log(formInput.current.value)
}, [formInput])
useEffect(()=>{
passSave(save)
}, [passSave, save])
return (
<input type="text" ref={formInput} />
)
}
export default PersonalInformation;
Related
I am trying to pass the value of the text area from some component in reactjs to be used in another react component. the component value is stored in the first component in a useState hook so I want to access it in another component and run map() function around it . Is this possible in reactjs ? I don't want to put the whole thing in app.js because that is just plain HTML which I don't want. I want to use reactjs function components instead ?
first component:
import React, { useState, useRef, useEffect } from "react";
function Firstcomp() {
const [quotes, setQuotes] = useState(["hi there", "greetings"]);
const reference = useRef();
function sub(event) {
event.preventDefault();
setQuotes((old) => [reference.current.value, ...old]);
console.log(quotes);
return;
}
return (
<>
<div>
<div>
<div>
<h4>jon snow</h4>
</div>
<form onSubmit={sub}>
<textarea
type="textarea"
ref={reference}
placeholder="Type your tweet..."
/>
<button type="submit">Tweet</button>
</form>
{quotes.map((item) => (
<li key={item}>{item}</li>
))}
{/* we can use card display taking item as prop where it
will do the job of filling the <p> in card entry */}
</div>
</div>
</>
);
}
export default Firstcomp;
second component
import React from "react";
function SecondComp(props) {
return (
<div>
<p>{props.message}</p>
</div>
);
}
export default Secondcomp;
Use a global management state like Recoil, Redux ot Context
import React from 'react';
export const UserContext = React.createContext();
export default function App() {
return (
<UserContext.Provider value="Reed">
<User />
</UserContext.Provider>
)
}
function User() {
const value = React.useContext(UserContext);
return <h1>{value}</h1>;
}
on the exemple above we used useContext hook to provide a global variable "value", even its not declared directly in User component, but you can use it by calling the useContext hook.
in this exemple the return value in the user component is "Reed"
So I have a Operations.js Component which gets rendered when a particular button in the parent component(ParamsForm.js) gets toggled. Now what I want is that when the form in the parent component gets submitted I want the data of the parent component form fields as well as the data of the child component form fields to get logged on the console . Is there any way to achieve this ???
ParamsForm.js
import React, { useState, useEffect } from 'react'
import { Form } from 'react-bootstrap'
import styles from '../style.module.css'
import Operations from './Operations'
const ParamsForm = () => {
const[isToggled,setIsToggled] = useState(false)
return (
<div className={styles.paramFormsContainer}>
<Form>
<button className={styles.paramFormsBtn}>http://localhost:3000/</button>
<input style={{flex : 1 }} type="text"></input>
<button type='button' onClick={()=>setIsToggled(!isToggled)} className={styles.pathParamFormsBtn}>Path Params</button>
{isToggled && <Operations></Operations>}
</Form>
</div>
)
}
export default ParamsForm
Operations.js
import React, { useEffect, useState } from 'react'
import styles from '../style.module.css'
import {FaInfo,FaFileInvoiceDollar} from 'react-icons/fa'
import ReactTooltip from "react-tooltip";
const Operations = () => {
const[isToggled,setIsToggled] = useState(true)
const[paramsList,setParamsList] = useState([{params: ""}])
useEffect(()=>{
console.log(paramsList)
console.log(isToggled)
},[isToggled])
const handleParamAdd = () =>{
setParamsList([...paramsList,{params:""}])
}
const handleParamRemove = (index) =>{
const list = [...paramsList]
list.splice(index,1)
setParamsList(list)
}
const handleParamsChange = (e,index)=>{
const{name,value} = e.target
const list = [...paramsList]
list[index][name] = value
setParamsList(list)
}
return (
<div >
<div className={styles.operationsBtnContainer}>
</div>
{isToggled && paramsList.map((singleParam,index)=>(<div key={index} className={styles.pathParamsFormParentContainer}>
<div className={styles.pathParamsFormChildContainer}>
<form>
<input name='name' value={singleParam.name} onChange={(e)=>handleParamsChange(e,index)} placeholder="Name..." style={{flex : 1 }} type="text"></input>
<select>
<option>any</option>
<option>string</option>
<option>number</option>
<option>integer</option>
<option>array</option>
</select>
<input placeholder="Description..." style={{flex : 1 }} type="text"></input>
{/* <button><FaFileInvoiceDollar></FaFileInvoiceDollar></button> */}
<button data-tip data-for="requiredTip"><FaInfo></FaInfo></button>
<ReactTooltip id="requiredTip" place="top" effect="float">
required
</ReactTooltip>
<button type='button' className={styles.addParamsBtn} onClick={handleParamAdd}><span>Add Parameter</span></button>
<button type='button' className={styles.removeParamsBtn} onClick={()=>handleParamRemove(index)}><span>Remove Parameter</span></button>
</form>
</div>
</div>)) }
</div>
)
}
export default Operations
There is no submit button in the parent component form, so you can't do anything when it's submitted.
Learn about putting answers of forms in state here
I would store every answer in its own state variable in the parent component, and pass the state set functions of the answers needed in the child to the child through props. You can then set the state through those functions in the child component and the parent component will have the state stored there already.
make a new piece of state for each answer
const [answers, setAnswer1] = useState("default")
pass the state to the child component via props
First, change the arguments of the child component's function to ({setAnswer1, setAnswer2, etc...})
Then pass the props to the child
<Operations setAnswer1={setAnswer1} setAnswer2={setAnswer2} etc.../>
handle input change, paste this inside the parent & child components
handleInputChange(event, setStateCallback) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
setStateCallback(value)
}
pass this function to each input, do the same in both parent and child components
<input onChange={(event) => handleInputChange(event, setAnswer1)}/>
after all this, you're ready to handle the submit event
Copy this function into the parent component
handleSubmit(event){
event.preventDefault()
console.log("answer 1's state: ", answer1)
console.log("answer 2's state: ", answer2)
// And so on for each piece of state
}
Above is not the cleanest solution, but it works. You could look into a for loop that takes an array of state variables and prints the value or something like that.
You also with need to add onSubmit={handleSubmit} to the form component in the parent component.
I'm fairly new to React JS and I've been working on one project where I need to first get data from the firebase firestore db and then display it on the page by creating new elements. I need to accomplish this without the whole page refreshing and only the rendered elements being displayed. I have fetched the data from the db and have put it into an array, after that I was trying to use the map function to go through the array and return an h1 element containing the data but it was not showing up on the page. I did a console.log on the data and it's showing up in the console as expected. My main question is, how do I return an h1 elements using the map function that will contain the data from the array?
import React, {useRef} from "react";
import {useHistory} from 'react-router-dom';
import '../connectApp.css';
import Navbar from './Navbar.js';
import SideBar from './sideBar.js';
import {db} from '../fbConfig';
import {auth} from '../fbConfig';
import ReactDOM from 'react-dom'
const App = () => {
const history = useHistory();
const data = [];
if(localStorage.getItem("isAuth") === 'null') { //NOTE - we are only able to store strings in localStorage
history.push('/');
}
var docRef = db.collection("posts");
docRef.onSnapshot(snapshot => {
let changes = snapshot.docChanges();
console.log(changes);
changes.forEach(change => {
if(change.type == "added") {
data.push({
imageUploader:change.doc.data().imageUploader,
imageCaption:change.doc.data().imageCaption,
imageUrl:change.doc.data().imageUrl
})
}
})
return <div>
{data.map(data => {
console.log(data)
})}
</div>
})
return (
<div className="container">
<Navbar />
<SideBar />
</div>
);
}
export default App
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
I found that by using the this.state method I was able to set a state for my data, store my data in an array, update the state, and then use the map function as was done previously to get the data and display it in a div.
I have two react components:
Component1 renders a Map with MapboxGl.
Component2 renders a button that when clicked, makes an API call and fetches Geojson data and stores it into a variable.
Problem:
Component1 needs the Geojson data to draw a route on the map.
How can I pass the the data from component2 to component1?
I tried with exporting the variable from component2 and importing it in component1, but it doesn't work.
Any help would be much appreciated.
In this case, I recommend using either React Context or Redux.
Here's the gist with context (it's a bit simpler imho than Redux). The example is based off this article.
import React, { createContext, useContext, useState } from 'react'
const GeoJSONContext = createContext({}) // this will be the geojson data.
const GeoJSONProvider = GeoJSONContext.Provider
const ButtonContainer = (props) => {
const { setGeoJSON } = useContext(GeoJSONContext)
return <Button onClick={() => setGeoJSON(await fetchGeoJSON())}>
Get Data
</Button>
}
const MapContainer = (props) => {
const { geoJSON } = useContext(GeoJSONContext)
return <Map data={geoJSON} />
}
const App = (props) => {
const [geoJSON, setGeoJSON] = useState([])
return (<GeoJSONProvider value={{ geoJSON, setGeoJSON }}>
<MapContainer />
<ButtonContainer />
</GeoJSONProvider>)
}
You can pass a function to the Button component as prop and call it when data is received. This means the container component hosting the map and the button component needs to keep state for the returned data. The state is then passed as property to the Map component
If the 2 components that you are having are independent to each other then what you probably need is a state container such as react-redux. Wrap the 2 components in a new component and move all the data that needs to be shared in the store. Otherwise go by Wakeel's answer - props.
I'm not understanding some ReactJs behavior and would need some help.
I have a Root Functional Component ("Index"), that contains another functional Component ("Preview").
That Preview component contains several other Functional Components ("InlineField").
The app is a simple form, where InlineField is component that renders an input and also contains a state to know if the field is "opened" or "closed" (when close it is displayed as a text, when open it is displayed as an input).
The global state is defined using hooks ad the "Index" level and moved down to the field through props (I've tried the same using Context). This state contains all form values.
The InlineField Component uses hook to maintain its local state only (is open / is closed).
When a an input is changed it updates the state (Index level) which triggers a re-render of the Index as well as its children.
This translate into the currently edited field (InlineField Component with local state = open) to refresh and lose its state value.
My question:
How can I make sure these InlineField Components retain their state even after updating global state?
I could simply move that InlineField Component state to the global state too, but I don't think it makes much sense.
I must be getting something wrong...
Thanks!
Edit: added code sample
Index Component:
import React, { useState, useEffect } from "react"
import Layout from "../components/layout"
const IndexPage = () => {
const [formValues, setFormValues] = useState({
name: 'Myname',
email: 'myemail#mail.com',
})
const onFormValueChange = (key, value) => {
setFormValues({...formValues, [key]: value})
}
return (
<Layout>
<Preview
key="previewyaknow"
formValues={formValues}
onFieldChange={setFormValues}
/>
</Layout>
)
}
export default IndexPage
Preview Component:
import React from 'react'
import { Box, TextField } from "#material-ui/core"
import { InlineField } from './inlineField'
export const Preview = ({formValues, onFieldChange}) => {
return (
<>
<Box display="flex" alignItems="center">
<InlineField
value={formValues.email}
onChange={onFormValueChange}
id="email"
field={<TextField value={formValues.email}/>>>}
/>
</>
)
}
InlineEdit Component
import React, { useState, useEffect } from "react"
export const InlineField = ({onChange, value, id, field}) => {
const [isEdit, setIsEdit] = useState(false)
const onBlur = (e) => {
setIsEdit(false)
}
let view = (<div>{value}</div>);
if (isEdit) {
view = (
<FieldContainer className={classes.fieldContainer}>
{React.cloneElement(field, {
'onBlur': onBlur,
'autoFocus': true,
'onChange': (e) => {
onChange(id, e.target.value)
}
})
}
</FieldContainer>
)
}
return (
<div onClick={()=>setIsEdit(!isEdit)}>
{view}
</div>
)
}