I'm trying to map trough my context in React but it won't render anything. (No error messages either). My app is wrapped with the context provider.
My context
import { createContext, useState } from "react";
export const ExpenseContext = createContext();
export const ExpenseProvider = ({ children }) => {
const [expenseType, setExpenseType] = useState();
const [description, setDescription] = useState();
const [value, setValue] = useState();
return (
<ExpenseContext.Provider
value={{
expenseType,
description,
value,
setExpenseType,
setDescription,
setValue,
}}
>
{children}
</ExpenseContext.Provider>
);
};
Where I want to render
import React from "react";
import { useContext } from "react";
import { ExpenseItem } from "./ExpenseItem";
import { ExpenseContext } from "../ExpenseContext";
export const ExpenseList = () => {
const context = useContext(ExpenseContext);
return (
<div>
{Object.keys(context).map((key) => (
<ExpenseItem
expenseType={key.expenseType}
description={key.description}
value={key.value}
></ExpenseItem>
))}
</div>
);
};
My item component
In my item component I'm just rendering {props.description} as a test.
Context data
My context gets data from a form like this:
import React from "react";
import classes from "./Form.module.css";
import { IoIosAddCircleOutline } from "react-icons/io";
import { useContext, useState } from "react";
import { ExpenseContext } from "../ExpenseContext";
export const Form = () => {
const [errorDesc, setErrorDesc] = useState(false);
const [errorSelect, setErrorSelect] = useState(false);
const [errorVal, setErrorVal] = useState(false);
const context = useContext(ExpenseContext);
const onSubmit = (e) => {
e.preventDefault();
if (!context.expenseType) {
setErrorSelect(true);
} else {
setErrorSelect(false);
}
if (!context.description) {
setErrorDesc(true);
} else {
setErrorDesc(false);
}
if (!context.value || isNaN(context.value)) {
setErrorVal(true);
} else {
setErrorVal(false);
}
};
return (
<form className={classes.form} onSubmit={onSubmit}>
<select
className={classes.select}
name="select"
id=""
onChange={(e) => context.setExpenseType(e.target.value)}
>
<option value="" disabled selected>
Expense Type
</option>
<option value="Savings">Savings</option>
<option value="Investments">Investments</option>
<option value="Expenses">Expenses</option>
</select>
<label htmlFor="select">
{errorSelect ? "Please choose an expense type!" : ""}
</label>
<h2 className="">Tracking Expenses</h2>
<hr />
<div className={classes.wrapper}>
<div className={classes.formGroup}>
<input
type="text"
placeholder="Description"
name="description"
className={
errorDesc ? classes.descriptionError : classes.description
}
onChange={(e) => context.setDescription(e.target.value)}
/>
<label htmlFor="description">
{errorDesc ? "Please add a description!" : ""}
</label>
</div>
<div className={classes.formGroup}>
<input
type="text"
placeholder="Amount"
name="value"
className={errorVal ? classes.valueError : classes.value}
onChange={(e) => context.setValue(e.target.value)}
/>
<label htmlFor="value">
{errorVal ? "Please add a valid value!" : ""}
</label>
</div>
<div className={classes.formGroup}>
<input type="submit" className={classes.submit} />
</div>
</div>
</form>
);
};
When you're mapping over Object.keys(context), what you're actually mapping over is ["expenseType","description","value","setExpenseType","setDescription","setValue"], because these are the keys of the object your context provides
Because those are strings, key.expenseType and key.description are undefined.
From what I have read I am pretty sure you're trying to do something else, you probably want your context to provide an array of objects which you map over, something like
import { createContext, useState } from "react";
export const ExpenseContext = createContext();
export const ExpenseProvider = ({ children }) => {
const [expenses, setExpenses] = useState([]);
return (
<ExpenseContext.Provider
value={{
expenses, setExpenses
}}
>
{children}
</ExpenseContext.Provider>
);
};
Where the members of expenses are object with the properties of expenseType, description and value, am I correct?
Related
Thanks for the help in advance. Currently, I am learning react. As a part of learning, I'm working on a basic expense listing app. In it, I was able to list the data entered through the form. But the problem is that when the new data is entered the previous one gets disappeared. I want to list the current as well as the previous data. Can anyone help me to resolve this.
App.js
import React, { useState, Fragment } from "react";
import ExpenseAddForm from "./ExpenseAddForm/ExpenseAddform";
import ExpenseList from "./ExpenseList/ExpenseList";
const App = () => {
const [expensesData, setExpensesData] = useState("");
const FormData = (datas) => {
setExpensesData(datas);
};
return (
<Fragment>
<div className="new-expense">
<ExpenseAddForm FormData={FormData} />
</div>
<ExpenseList listDatas={expensesData}></ExpenseList>
</Fragment>
);
};
export default App;
ExpenseAddform.js
import React, { useState } from "react";
import "./ExpenseAddForm.css";
const ExpenseAddForm = (props) => {
const [title, setTitle] = useState("");
const [amount, setAmount] = useState("");
const [date, setDate] = useState("");
const titleHandler = (e) => {
setTitle(e.target.value);
};
const amountHandler = (e) => {
setAmount(e.target.value);
};
const dateHandler = (e) => {
setDate(e.target.value);
};
const formSubmitHandler = (event) => {
event.preventDefault();
const formData = {
title: title,
amount: amount,
date: date,
};
props.FormData(formData);
setTitle("");
setAmount("");
setDate("");
};
return (
<form onSubmit={formSubmitHandler}>
<div className="new-expense__controls">
<div className="new-expense__control">
<label>Title</label>
<input type="text" onChange={titleHandler} value={title} />
</div>
<div className="new-expense__control">
<label>Amount</label>
<input type="number" onChange={amountHandler} value={amount} />
</div>
<div className="new-expense__control">
<label>Date</label>
<input type="date" onChange={dateHandler} value={date} />
</div>
</div>
<div className="new-expense__actions">
<button type="submit">Cancel</button>
<button type="submit">Add Expense</button>
</div>
</form>
);
};
export default ExpenseAddForm;
ExpenseList.js
import React from "react";
import ExpenseItem from "./ExpenseItem/ExpenseItem";
import ExpenseDateFilter from "./ExpenseDateFilter/ExpenseDateFilter";
import "./ExpenseList.css";
const ExpenseList = (props) => {
return (
<div className="expenses">
<ExpenseDateFilter />
<ExpenseItem expenseInfos={props.listDatas} />
</div>
);
};
export default ExpenseList;
ExpenseItem.js
import React from "react";
// import ExpenseDate from "../ExpenseDate/ExpenseDate";
import "./ExpenseItem.css";
const ExpenseItem = (props) => {
return (
<div className="expense-item">
{/* <ExpenseDate enteredDate={props.expenseInfos.date} /> */}
<div className="expense-item__description">
<h2>{props.expenseInfos.title}</h2>
{/* <div className="expense-item__price">${props.expenseInfos.amount}</div> */}
</div>
</div>
);
};
export default ExpenseItem;
ExpenseDateFilter.js
import React from "react";
const ExpenseDateFilter = () => {
return (
<div className="expenses-filter">
<div className="expenses-filter__control">
<label>Filter by year</label>
<select>
<option value="2022">2022</option>
<option value="2021">2021</option>
<option value="2020">2020</option>
<option value="2019">2019</option>
</select>
</div>
</div>
);
};
export default ExpenseDateFilter;
The problem with your code is on each submit your overriding the expensesData with the new data
instead you need to do
const FormData = (datas) => {
setExpensesData([...expensesData,datas]);
};
now you have list of data next you need to handle multiple expenses data in ExpenseList.js
{props.listDatas.map((data,key)=>
<ExpenseItem expenseInfos={data} />
)}
and it should be done here is a link of a working example https://codesandbox.io/s/xenodochial-grass-g2k1fh?file=/src/App.js:467-476
i'm just learning react and What I'm trying to achieve here is rendering a name which is filled by the user in a form, on a Banner below. I attached a picture to illustrate. I use a different component for the Banner, Form, and of course app.js. Everything works fine and the name succecfully appears in the console.log, but I can't seem to make the name to display on the Banner. Here is my code:
Thanks!
App.js:
import Form from './components/Form'
import { useState } from 'react'
import TableDisplay from './components/TableDisplay'
import Banner from './components/Banner'
const App = () => {
const [showBanner, setShowBanner]=useState(false)
//const[name, setName] = useState('')
//const[credits, setCredits] = useState('')
const [rows, setRows] = useState([
{
id:1,
description:'',
semester:'',
prefix:'ENG',
number:'368/371',
grade:'',
editing:''
},
{
id:2,
description:'',
semester:'',
prefix:'',
number:'',
grade:'',
editing:''
},
{
id:3,
description:'',
semester:'',
prefix:'',
number:'',
grade:'',
editing:''
},
])
const getFormInfo = (info) => {
console.log(info.name)
return this.info.name;
}
return (
<>
<Form onAdd={getFormInfo} onAdd2={() =>
setShowBanner(!showBanner)}/>
{showBanner ? <Banner onAdd={getFormInfo}/> : null}
<TableDisplay rows={rows}/>
</>
);
}
export default App;
Form.js:
const Form = ({ onAdd,onAdd2 }) => {
const[name, setName] = useState('')
const[credits, setCredits] = useState('')
const onSubmit = (e) => {
e.preventDefault();
onAdd({name,credits})
setName('');
setCredits('');
}
return (
<form onSubmit={onSubmit} >
<div className="mb-3">
<label className="form-label">Student Name:</label>
<input value={name}
onChange={(e) =>
setName(e.target.value)}
type="text"
className="form-control"
id="exampleInputEmail1"
aria-describedby="emailHelp"/>
</div>
<div className="mb-3">
<label className="form-label">Total Transfer Credits:</label>
<input value={credits}
onChange={(e) =>
setCredits(e.target.value)}
type="number"
className="form-control"
id="exampleInputPassword1"/>
</div>
<button onClick={onAdd2}
type="submit" className="btn btn-primary">Submit</button>
</form>
)
}
export default Form
Banner.js:
import React from 'react'
import { useState } from "react"
import Form from './Form';
const Banner= ({onAdd}) => {
const getName = () => {
}
return (
<div>
<h4 className="bg-primary text-white text-center p-2">
Writing emphasis for
</h4>
</div>
)
}
export default Banner
As stated in the question I want to call a function declared in another component. Here's some example data,
function BookingTable() {
const renderTableData = (startId) => {
let id = startId;
}
}
export default BookingTable;
How do i access the renderTableData from another component?
If the function should be accessible is the child component of the component which has the function. Then you can pass the function through props.
But the best option for this is context api. With that you can access the function in multiple components.
Context api helps you share the states and functions of a component
with other components inside the particular project.
In Filecontext.jsx you can see createContext which helps you in creating a context.
In App.jsx, we have created the states and functions which has to be shared among the components and wrapped the components which can access the datas with that context by importing it.
In Formcomponent.jsx, I am using useContext to use the states and functions created in the App.jsx.
Filecontext.jsx
import { createContext } from 'react'
export const Filecontext = createContext({});
App.jsx
import { Filecontext } from './Contexts/Filecontext';
import { useState } from 'react'
function App() {
const [name, setName] = useState("")
const [email, setEmail] = useState("")
const [mobileno, setMobileno] = useState("")
const showAlert = () => {
alert(`Hello ${name}`);
}
return (
<div className="App">
<Filecontext.Provider value={{ name, setName, email, setEmail, mobileno, setMobileno, showAlert }}>
<Formcomponent />
<Listcomponent />
</Filecontext.Provider>
</div>
);
}
export default App;
Formcomponent.jsx
import { Filecontext } from '../Contexts/Filecontext';
import { useContext } from 'react'
export default function Formcomponent() {
const { setName, setEmail, setMobileno, showAlert } = useContext(Filecontext)
return (
<>
<div className="form-group">
<label>Name : </label>
<input type="text" onChange={(e) => { setName(e.target.value) }} />
</div>
<div className="form-group">
<label>Email : </label>
<input type="email" onChange={(e) => { setEmail(e.target.value) }} />
</div>
<div className="form-group">
<label>Mobile No : </label>
<input type="number" onChange={(e) => { setMobileno(e.target.value) }} />
</div>
<div className="form-group">
<input type="submit" value="submit" onClick={() => { showAlert() }} />
</div>
</>
)
}
function BookingTable() {
const renderTableData = (startId) => {
let id = startId;
}
return (
<BookingTable2 renderTableData={renderTableData} />
)
}
export default BookingTable;
const BookingTable2 = ({renderTableData}) => {
const onClickHandler = () => {
renderTableData()
}
return (
<button onClick={onClickHandler}>Calling func from child component</button>
)
}
export default BookingTable;
Bear in mind that React FC are just JS functions that return JSX (in most cases) so you can't access variables that were declared inside of them from outside of the component. The solution to that problem would be to pass the function as props to the child components.
I am trying to build a shopping cart that takes items and quantities from a form input adding to local storage and displays them as a list of items in another component. Each list has a checkbox which on checking I want to move the item to the checked list. I am hitting an infinite rerender when trying to add logic to add checked items in a checked list. Here is my code: sandbox https://codesandbox.io/s/fervent-carson-gchmtt?file=/src/App.jsAny help in resolving an issue will be much appreciated.
ShoppingItemList.js
import { React, useState, useCallback, useEffect } from "react";
import { Link } from "react-router-dom";
import "./ShoppingItemList.css";
const ShoppingItemList = (props) => {
const [checked, setChecked] = useState(props.shoppingList);
// useEffect(()=>{
// setChecked(props.shoppingList)
// },[])
// Add/Remove checked items from the list
const handleCheck = useCallback(
(index) => {
let updatedList = [...props.shoppingList];
updatedList[index].isSelected = !updatedList[index].isSelected;
setChecked(updatedList);
console.log("checked " + checked);
},
[checked]
);
return (
<div className="container">
<div className="title">Shopping List:</div>
<div className="list-container">
{props.shoppingList.map((item, index) => (
<ul key={item.id}>
<input value={item} type="checkbox" onChange={handleCheck(index)} />
<div className="child">{item.item}</div>
<div className="child">{item.quantity}</div>
</ul>
))}
</div>
<Link to="/">Shopping Form</Link>
<br></br>
{/* <div>
{`Items checked are: ${checkedItems}`}
</div> */}
</div>
);
};
export default ShoppingItemList;
App.js
import { React, useState,useEffect } from "react";
import ShoppingListForm from "./Components/ShoppingListForm";
import ShoppingItemList from "./Components/ShoppingItemList";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
function App() {
let initShoppingItemList = JSON.parse(localStorage.getItem("shoppedItem") || "[]");
const [shoppingItem, setShoppingItem] = useState(initShoppingItemList);
const addShoppedItemHandler = (shoppedItem) => {
// const shoppingListWId = shoppingItem.map((n,i) => {
// return n['id'] = i;
// })
let id;
shoppingItem.length===0?id=0: id = shoppingItem[shoppingItem.length-1].id+1;
const shoppingListWId = {
id:id,
item:shoppedItem.item,
quantity:shoppedItem.quantity,
isSelected:shoppedItem.isSelected
};
//setShoppingItem(...shoppingItem,shoppingListWId)
setShoppingItem([...shoppingItem,shoppingListWId])
// setShoppingItem((prevShoppingItem) => {
// return [shoppingItem, ...prevShoppingItem];
// });
console.log(shoppingItem);
};
useEffect(() => {
if (initShoppingItemList !== 0)
localStorage.setItem("shoppedItem", JSON.stringify(shoppingItem));
},[initShoppingItemList,shoppingItem]);
return (
<Router>
<Routes>
<Route
path="/"
element={
<ShoppingListForm onSavedShoppingData={addShoppedItemHandler} />
}
/>
<Route
exact
path="/itemList"
element={<ShoppingItemList shoppingList={shoppingItem} />}
/>
</Routes>
</Router>
);
}
export default App;
ShoppingListForm.js
import { React, useState } from "react";
import { useNavigate } from "react-router-dom";
import "./ShoppingListForm.css";
const ShoppingListForm = (props) => {
const navigate = useNavigate();
const [enteredItem, setEnteredItem] = useState("");
const [enteredQuantity, setQuantity] = useState(0);
const itemChangeHandler = (event) => {
console.log("inputbox " + event.target.value);
setEnteredItem(event.target.value);
};
const submitHandler = (event) => {
event.preventDefault();
let shoppingData;
if (enteredItem !== undefined && enteredQuantity !== 0) {
shoppingData = {
item: enteredItem,
quantity: enteredQuantity,
isSelected: false
};
props.onSavedShoppingData(shoppingData);
setEnteredItem("");
setQuantity("");
navigate("/itemList");
} else {
alert("Enter Item & select quantity");
}
};
const dropdownChangeHandler = (event) => {
console.log(event.target.value);
setQuantity(event.target.value);
};
return (
<form className="shop-item" onSubmit={submitHandler}>
<div className="shop-item__controls">
<div className="shop-item__control">
<label>Title</label>
<input
type="text"
value={enteredItem}
placeholder="Add an item..."
onChange={itemChangeHandler}
/>
</div>
<div className="shop-item__control">
<label>Item Quantity</label>
<select value={enteredQuantity} onChange={dropdownChangeHandler}>
<option value="0">0</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
</div>
</div>
<div className="shop-item__actions">
<button type="submit"> Save Item </button>
</div>
</form>
);
};
export default ShoppingListForm;
Your call in ShoppingList.js is wrong. Instead of onchange={handleCheck(index)} you need to make it an anonymous function to pass in an argument the way you have it: onChange={() => handleCheck(index)}`
Line 45 of ShoppingItemList.js
Your fixed sandbox: https://codesandbox.io/s/confident-wood-1jonhs
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);
});