Passing custom function to imported component in React - reactjs

This is a beginner react question as I am learning.
I have two files hero.js and sidebar.js. There are two functions I have in hero.js that need to be accessed from buttons in sidebar.js (namely: onSave and onRestore).
How do I pass those functions down. I realize I probably need to do something like `this.onSave' but not sure how as still new to react.
hero.js
import React, { useState, useRef, useCallback } from "react";
import ReactFlow, {removeElements, addEdge,Controls, updateEdge, ReactFlowProvider, useZoomPanHelper,} from "react-flow-renderer";
import Sidebar from './sidebar';
import localforage from 'localforage';
localforage.config({
name: 'react-flow-docs',
storeName: 'flows',
});
...
const { transform } = useZoomPanHelper();
const onSave = useCallback(() => {
if (rfInstance) {
const flow = rfInstance.toObject();
localforage.setItem(flowKey, flow);
}
}, [rfInstance]);
const onRestore = useCallback(() => {
const restoreFlow = async () => {
const flow = await localforage.getItem(flowKey);
if (flow) {
const [x = 0, y = 0] = flow.position;
setElements(flow.elements || []);
transform({ x, y, zoom: flow.zoom || 0 });
}
};
restoreFlow();
}, [setElements, transform]);
return(
<div className="hero_container" >
<ReactFlowProvider>
<div className='rowC'>
<div className='reactflow-wrapper' style={{ height: 500, width: 1200 }} ref={reactFlowWrapper} >
<ReactFlow
elements={elements}
onLoad={onLoad}
onElementsRemove={onElementsRemove}
onEdgeUpdate={onEdgeUpdate}
onConnect={onConnect}
onDragOver={onDragOver}
onDrop={onDrop}
deleteKeyCode={46} /* 'delete'-key */
>
<Controls />
</ReactFlow>
</div>
<div>
<Sidebar />
</div>
</div>
</ReactFlowProvider>
</div>
);
};
sidebar.js
mport React from 'react';
const Sidebar = () => {
const onDragStart = (event, nodeType) => {
event.dataTransfer.setData('application/reactflow', nodeType);
event.dataTransfer.effectAllowed = 'move';
};
return (
<div className='side_controls'>
<div className='add_node_area'>
<h5>Add Nodes</h5>
<div className="description">You can drag these nodes to the pane on the left.</div>
<div className="react-flow__node-input" onDragStart={(event) => onDragStart(event, 'input')} draggable>
Input Node
</div>
<div className="react-flow__node-default" onDragStart={(event) => onDragStart(event, 'default')} draggable>
Default Node
</div>
<div className="react-flow__node-output" onDragStart={(event) => onDragStart(event, 'output')} draggable>
Output Node
</div>
</div>
<div className='save_restore_buttons'>
<button onClick={onSave}>save</button>
<button onClick={onRestore}>restore</button>
</div>
</div>
);
};
export default Sidebar;

You can access these functions inside Sidebar component by passing them as props
<Sidebar onSave={onSave} onRestore={onRestore} />
And in sidebar.js
const Sidebar = (props) => {
const {onSave, onRestore} = props;

Just pass it in as props from hero.js
<Sidebar onSave={onSave} onRestore={onRestore} />
then use it in sidebar.js
const Sidebar = (props) => {
const {onSave, onRestore} = props;
return (
<div className='save_restore_buttons'>
<button onClick={onSave}>save</button>
<button onClick={onRestore}>restore</button>
</div>
)
}

you should pass your functions as props to the Component that you want to use in there, and then in the destination component, you can use those functions with props.[prop-name]
For more information, you can read this doc.
hero.js
<Sidebar onSave={onSave} onRestore={onRestore}/>
Sidebar.js
<div className='save_restore_buttons'>
<button onClick={props.onSave}>save</button>
<button onClick={props.onRestore}>restore</button>
</div>

Related

Data is not displaying in react components

I don't understand why my fetched data is not displaying.
Data is fetched properly when i check by console.log() however nothing shows up in my JSX section. Code looks alight too. Anyone have idea what is wrong here?
import React from "react";
import { useEffect, useState } from "react";
import axios from "axios";
export const ConcertLatest = () => {
const [concerts, setConcerts] = useState([]);
useEffect(() => {
const loadConcerts = async () => {
const response = await axios.get("/data/concerts");
const rawData = response.data;
const filteredData = rawData.filter((concert) => {
//let date = new Date(concert.datum);
// let newestDate = new Date("2022-09-29");
return concert.datum >= "2022-09-30";
});
setConcerts(filteredData);
};
loadConcerts();
}, []);
if (!concerts.length) {
return <p>Loading...</p>;
}
console.log(concerts); // getting full populated objects
return (
<div>
<h1>Newest concerts </h1>
<div>
<div className="card">
<img src={concerts.image} style={{ width: 100 }} />
<div className="card-text">
<div>
{concerts.map((concert) => {
(<h1>{concert.name}</h1>), (<h2>{concert.datum}</h2>);
})}
</div>
</div>
</div>
</div>
</div>
);
};
Change syntax of map function, either use return keyword with curly braces or just use round braces without return keyword. Eg
<div>
{concerts.map((concert) => {
return (
<React.Fragment>
(<h1>{concert.name}</h1>), (<h2>{concert.datum}</h2>)
</React.Fragment>
)
})}
</div>
Or
<div>
{concerts.map((concert) => (
<React.Fragment>
(<h1>{concert.name}</h1>), (<h2>{concert.datum}</h2>)
</React.Fragment>
))}
</div>

Dynamic react toggle button for multiple elements

I need to toggle multiple blocks with true/false in react state, but true and false works for all blocks at the same time.
import { useState } from "react";
...
const [toggleThisElement, setToggleThisElement] = useState(false);
...
{
data.map((d, id) => {
return (
<div className="single-history" key={id}>
<div className="h-head">
click this div for toggle h-info block
</div>
<div className="h-info">toggling block</div>
</div>
)
})
}
Currently, all your toggle items share the same state. To avoid that create a separate component for toggling logic called ToggleItem as below.
import { useState } from "react";
const ToggleItem = ({ discription, id }) => {
const [toggleThisElement, setToggleThisElement] = useState(false);
return (
<div className="single-history" key={id}>
<button
className="h-head"
onClick={() => setToggleThisElement((prev) => !prev)}
>
click this btn for toggle h-info block {id}
</button>
{toggleThisElement && <div className="h-info">{discription}</div>}
</div>
);
};
export default function App() {
const data = ["first", "second", "third"];
return (
<>
{data.map((d, id) => {
return <ToggleItem id={id} discription={d} />;
})}
</>
);
}

Button 'more' in a popup box doesn’t work right with React hook useState

I created a component (sort of popup box) which displays a sign of horoscope, there’s an image and description. The popup box works correctly. I added a button ‘more’ to see more description, so I used a useState for it, but it doesn’t work, when I click on it doesn't show the rest of the text.
Thanks for your help !
const Modal = ({
children, visible, hide, fermer, more,
}) => {
const popup = `popup ${visible ? 'block' : 'hidden'}`;
return (
<div className={popup}>
{fermer ? null : (
<button className="close" onClick={hide} type="button">X</button>
)}
{children}
<button className="more" onClick={more} type="button">more</button>
</div>
);
};
export default Modal;
import './App.css';
import { useState } from 'react';
import Element from './Element';
import Modal from './Modal';
import Bd from './Bd';
function App() {
const bd = Bd.map((element) => (
<Element
nom={element.nom}
image={element.image}
description={element.description}
modulo={element.modulo}
/>
));
const [year, setYear] = useState('');
function handleChange(event) {
setYear(event.target.value);
}
const [signe, setSigne] = useState([]);
const [vis, setVis] = useState(false);
const [desc, setDesc] = useState(true);
function handleSubmit() {
setVis(true);
const yearModulo = Number(year) % 12;
Bd.map((element) => (
yearModulo === element.modulo ? setSigne(
[<h1>{element.nom}</h1>,
<div>{element.description.substr(0, 150)}</div>,
desc ? <div />
: <div>{element.description.substr(150, 600)}</div>,
<img src={`/images/${element.image}`} alt="" />,
],
) : false
));
}
return (
<div>
<div>
<input
className="text-center font-bold"
type="number"
id="year"
name="year"
value={year}
onChange={handleChange}
/>
<button type="submit" onClick={handleSubmit}>Valider</button>
</div>
<div className="flex flex-wrap">{bd}</div>
<Modal
visible={vis}
hide={() => setVis(false)}
more={() => setDesc(false)}
>
<div>
<div>{signe}</div>
</div>
</Modal>
</div>
);
}
export default App;
I would avoid storing in a local state a component (setSigne([<h1>{element.nom}</h1>,...). Prefer storing in the state the values that cannot be computed from other existing states, and generate the elements at rendering.
const [signe, setSigne] = useState(null);
function handleSubmit() {
setVis(true);
const yearModulo = Number(year) % 12;
setSigne(Bd.find(element => yearModulo === element.modulo));
}
// ...
<div>
{signe && <div>
<h1>{signe.nom}</h1>
<div>{signe.description.substr(0, 150)}</div>
{desc ? <div /> : <div>{signe.description.substr(150, 600)}</div>}
<img src={`/images/${signe.image}`} alt="" />
</div>}
</div>
Also, don’t forget to add a key prop when generating elements from an array:
const bd = Bd.map(element => (
<Element
key={element.nom}
// ...

Pass props to component: "Objects are not valid as a React child"

I'm trying to simply pass the Id of a clicked item to display on another page under a different component ("Cart") . At the bottom of the code below, I have a button containing <Cart test={product.id} /> which extracts the Id that I want to be displayed in "Cart" when the button is clicked.
However, I am instead getting an error message of:
Objects are not valid as a React child (found: object with keys
{history, location, match, staticContext}). If you meant to render a
collection of children, use an array instead.
Is there a simple syntax error?
import React, { useState, useEffect, Cart } from 'react';
import './../App.css';
import * as ReactBootStrap from 'react-bootstrap';
function Item(props) {
const [product, setProduct] = useState([]);
const [loading, setLoading] = useState(false);
const [quantity, setQuantity] = useState(1);
const [cost, setCost] = useState([]);
useEffect(async () => {
fetchItems();
}, []);
const itemId = props.match.params.item;
const fetchItems = async () => {
const data = await fetch('https://fakestoreapi.com/products/' + itemId);
const items = await data.json();
setProduct(items);
setLoading(true);
setCost(items.price);
};
function priceUSD(change) {
return change.toFixed(2);
}
useEffect(() => {
const newCost = quantity * product.price;
setCost(priceUSD(newCost));
}, [quantity]);
return (
<div className="App">
<h2>Item</h2>
<div className="gridContainer">
{loading ? (
<div key={itemId} className="productStyle">
<img src={product.image} className="productImage"></img>
<p>{product.title}</p>
<p>{product.description}}</p>
<p>${priceUSD(product.price)}</p>
<div className="quantity">
<button
className="btn minus-btn"
type="button"
onClick={quantity > 1 ? () => setQuantity(quantity - 1) : null}
>
-
</button>
<input type="text" id="quantity" value={quantity} />
<button className="btn plus-btn" type="button" onClick={() => setQuantity(quantity + 1)}>
+
</button>
</div>
<button type="button" onClick={() => <Cart test={product.id} />}>
Add to shopping cart ${cost}
</button>
</div>
) : (
<ReactBootStrap.Spinner className="spinner" animation="border" />
)}
</div>
</div>
);
}
export default Item;
Cart
import React, { useState, Item } from 'react';
import './../App.css';
import './Item.js';
function Cart(test) {
return (
<div className="App">
<p>{test}</p>
</div>
);
}
export default Cart;
Component props are objects. You can read more about them in the official documentation.
You can either destructure the props of the Cart component:
function Cart({test}) {
return (
<div className="App">
<p>{test}</p>
</div>
);
}
or use explicitly test property of props:
function Cart(props) {
return (
<div className="App">
<p>{props.test}</p>
</div>
);
}

Why does React.createPortal doesn't process event.stopPropagation?

I'm experimenting on a custom modal using React.createPortal, everything's working if I am only opening and closing the modal via buttons inside the modal and the default result is displayed on the component below but what I want to achieve is when I click the modal container (dark overlay div) it should close the modal, so I added an exithandler on it, but the problem is the default form button behavior inside the modal content doesn't work anymore even if there's an event.stopPropagation() function in the exithandler.
Here's the codesandbox sample of two modals, one with a form and one without: https://codesandbox.io/s/react-modals-2dnei
Modal container:
import React from "react";
import { createPortal } from "react-dom";
const modalRoot = document.getElementById("modal-root");
function Modal({ exitHandler, children }) {
return createPortal(
<div className="modal" onClick={exitHandler}>
{children}
</div>,
modalRoot
);
}
export default Modal;
Modal content 1:
import React from "react";
const HelloWorld = ({ user, onClose }) => {
return (
<div className="modal-content">
<div className="modal-header">
<div className="modal-title">
<h2>Greetings</h2>
</div>
<div className="modal-action">
<button type="button" onClick={onClose}>X</button>
</div>
</div>
<div className="modal-body">
<p>{user ? `Hello ${user}!` : "Greetings!"}</p>
</div>
</div>
);
};
export default HelloWorld;
Modal Content 2:
import React, { forwardRef, useEffect } from "react";
const SignUp = forwardRef((props, ref) => {
const { onClose, onSignUp, handleChange, inputError, user } = props;
useEffect(() => {
ref.current.focus();
});
return (
<div className="modal-content">
<div className="modal-header">
<div className="modal-title">
<h2>Sign Up</h2>
</div>
<div className="modal-action">
<button type="button" onClick={onClose}>X</button>
</div>
</div>
<div className="modal-body">
<form onSubmit={onSignUp} className="modal-form">
<label className="input-label">
<span>Name:</span>
<input
type="text"
onChange={handleChange}
value={user}
ref={ref}
className={inputError === true ? "input-error" : null}
/>
</label>
<input className="btn" type="submit" value="Submit" />
</form>
</div>
</div>
);
});
export default SignUp;
Main:
const App = () => {
const [modalState, setModalState] = useState(false);
const [modalId, setModalId] = useState(null);
const [inputError, setInputError] = useState(false);
const [user, setUser] = useState("");
const [userStatus, setUserStatus] = useState("");
const nameRef = useRef(null);
const handleShowModal = (e) => {
const modalId = e.target.id.toString();
return [setModalState(true), setModalId(modalId)];
};
const handleHideModal = (event) => {
event.stopPropagation(); // This doesn't work
setModalState(false);
};
const runSignUpResult = useCallback(() => {
handleHideModal();
setUserStatus(`Thank you ${user} for signing up!`);
}, [user]);
const handleValidation = useCallback(
(nameParameter) => {
if (nameParameter.length === 0) {
nameRef.current.focus();
setInputError(true);
} else {
return [setInputError(false), runSignUpResult()];
}
},
[runSignUpResult]
);
const handleSignUp = useCallback(
(e) => {
e.preventDefault();
const name = nameRef.current.value;
handleValidation(name);
},
[nameRef, handleValidation]
);
const handleChange = (e) => {
setUser(e.target.value);
};
const modal = modalState ? (
<Modal exitHandler={handleHideModal}>
{modalId === "greeting" ? (
<HelloWorld onClose={handleHideModal} user={user} />
) : (
<SignUp
onClose={handleHideModal}
onSignUp={handleSignUp}
handleChange={handleChange}
user={user}
ref={nameRef}
inputError={inputError}
modalId={modalId}
/>
)}
</Modal>
) : null;
return (
<div className="App">
<AppHeader />
<main className="app-body">
<section className="modal-btns">
<button id="greeting" className="btn" onClick={handleShowModal}>
Greetings!
</button>
<button id="signup" className="btn" onClick={handleShowModal}>
Sign Up
</button>
</section>
<section>{modal}</section>
<section className="user-status">
<h3>{user.length === 0 ? null : userStatus}</h3>
</section>
</main>
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Resources