I'm new to react and have encountered an issue which I haven't found any solution to for a while now.
The NEXT button is a child component of Form1 and is declared in App.js. The parameters of Form1 file is (props, {transform, fram}). The intention with props is to declare {props.children} in Form1 so that it allows the Next button to be shown by being implemented in App.js.
When implemented in this manner, the next button only seem to execute 1 function rather than 2 - handleNext() but not fram(). Fram() sets translateX value to form1. handleNext controls the state of CustomizedSteppers.
However, if the "props" is deleted from Form1, and the button is moved out of the tags and put for example above CustomizedSteppers tag, it executes both functions. I want the button to be implemented in the manner that is shown below but it does not work as intended
My App.js:
import {Form1, Form2, Form3, Form4} from './components';
import {Header} from './containers';
import {Section} from './containers';
import {Forms} from './containers'
import CustomizedSteppers from './components/stepper/demo';
const App = () => {
const [activeStep, setActiveStep] = React.useState(0);
const handleNext = () => {
setActiveStep((activeStep+1));
};
const [transform, transformit] = React.useState("TranslateX(0px)");
const fram = () => {
transformit("TranslateX(-900px)");
};
return (
<>
<Header />
<Section class="section">
<CustomizedSteppers activeStep={activeStep} handleNext={handleNext}/>
<Forms>
<Form1 transform={transform} fram={fram}>
<button id="B "onClick={() => {handleNext();fram();}}>NEXT</button>
</Form1>
<Form2 />
<Form3 />
<Form4 />
</Forms>
</Section>
</>
);
}
export default App;
My Form1.js:
export default function Form1(props, {transform, fram}){
return (
<div id='Form1' style={{transform: transform}}>
<p id="demo"></p>
<div class="btn-box-f1">
{props.children}
</div>
</div>
)
}
Instead of trying to call two functions, call the function that updates the state first, and then use useEffect to monitor that state change.
The useEffect:
useEffect(() => {
transformit('TranslateX(-900px)');
}, [activeStep]);
And the updated button:
<button id="B" onClick={handleNext}>NEXT</button>
Oh, and there's no need to have double parentheses in your setActiveStep function:
setActiveStep(activeStep + 1);
As far as I can tell, the issue is with the way you've declared the Form1 component's props. React components take only a single argument, the props object. function Form1(props, { transform, fram }) is invalid, the transform prop isn't accessible in the component, it's undefined.
Here's the working version:
function Form1({ children, transform }) {
return (
<div id="Form1" style={{ transform: transform }}>
<p id="demo"></p>
<div className="btn-box-f1">{children}</div>
</div>
);
}
I've dropped logs in both callbacks and correctly see both triggered and see the transform applied to the id="Form1" div element.
Here the "next" button was clicked, both callbacks logged, the active step state updated to 1, and the transform was applied.
Related
I am constructing some node objects in a function(prepareNodes) to pass to React Flow within a functional component A (lets say), and I have defined a custom node component(CardNode) stateless, which has a button. On button click it should trigger the function(prepareNodes) defined within Component A.
function ComponentA = ({ selectedNodes }) => {
const reactFlowWrapper = useRef(null);
const [elements, setElements] = useState([]);
const [edges, setEdges] = useState([]);
const prepareNode = async (nodeid) => {
//some service calls to fetch data and constuct nodes
setElements([ ...nodes]);
setEdges([...edges]);
}
return (
<ReactFlowProvider>
<div className="reactflow-wrapper" ref={reactFlowWrapper}>
<ReactFlow
nodes={elements}
edges={edges}
//some properties
>
</ReactFlow>
</div>
</ReactFlowProvider>
)
};
export default ComponentA;
function CardNode({ data }) {
const renderSubFlowNodes = (id) => {
console.log(id);
//prepareNode(id)
}
return (
<>
<Handle type="target" position={Position.Top} />
<div className="flex node-wrapper">
<button className="btn-transparent btn-toggle-node" href="#" onClick={() => renderSubFlowNodes(data['id']) }>
<div>
<img src={Icon}/>
</div>
</button>
</div>
<Handle type="source" position={Position.Bottom}/>
</>
);
}
export default CardNode;
I looked for some references online, and most of them suggest to move this resuable function out of the component, but since this function carries a state that it directly sets to the ReactFlow using useState hook, I dont think it would be much of a help.
Other references talks about using useCallback or useRefs and forwardRef, useImperativeHandle especially for functional component, Which I did not quite understand well.
Can someone suggest me a solution or a work around for this specific use-case of mine.
You can add an onClick handler to the each node, and within the node view you call this handler on click.
In the parent Component within the onClick handler you can call prepareNode as needed.
useEffect(() => {
setElements(
elements.map(item => {
...item,
onClick: (i) => {
console.log(i);
prepareNode();
},
})
)},
[]);
The classical approach is to have a parent object that defines prepareNode (along with the state items it uses) and pass the required pieces as props into the components that use them.
That "parent object" could be a common-ancestor component, or a Context (if the chain from the parent to the children makes it cumbersome to pass the props all the way down it).
I'm dealing with a problem passing a prop to a parent component from it's child.
The idea of the code that I'm trying to make work is a set of buttons in a header component that when clicked, load new component pages for other parts of the website. I'm dealing with a couple of smaller bugs that I can fix at another time but the primary issue is when I try to pass the results of the function for handling the switch and the values showing as 'undefined' once they get to the App component. I doubt I'm explaining it well so allow me to show the code.
Parent Component (App)
import React from "react";
import Header from "./Components/Header/Header";
import Footer from "./Components/Footer/Footer";
import Pane from "./Components/Pane/Pane";
import MainPane from "./Components/Pane/MainPane";
import BookViewPane from "./Components/Pane/BookViewPane";
import AddBookPane from "./Components/Pane/AddBookPane";
import SignInPane from "./Components/Pane/SignInPane";
import "./App.css";
const App = ()=>{
function LoadPaneHandler(props){
var NewPaneName=props.paneName;
// const NewPaneName={
// name: props.paneName
// };
// const NewPaneName=String(props);
console.log(NewPaneName);
switch(NewPaneName){
case 'MainPane':
return <MainPane />
case 'AddBookPane':
return <AddBookPane />
case 'BookViewPane':
return <BookViewPane />
case 'SignInPane':
return <SignInPane />
default:
return <Pane />
}
}
return(
<React.Fragment>
<Header switchPane={LoadPaneHandler} />
<main>
<LoadPaneHandler paneName="MainPane" />
</main>
<Footer />
</React.Fragment>
);
}
export default App;
Child Component (Header)
import React from "react";
import "./Header.css";
const Header=(props)=>{
var paneName="";
const switchPaneHandler=event=>{
event.preventDefault();
console.log(paneName);
props.switchPane(paneName);
}
return(
<header id="header">
<div id="header-title">
<h1>Library</h1>
</div>
<div id="header-buttons">
<button onClick={paneName="BookViewPane",switchPaneHandler}> View Books</button>
<button onClick={paneName="AddBookPane",switchPaneHandler}> Add Books </button>
<button onClick={paneName="SignInPane",switchPaneHandler}> Login</button>
</div>
</header>
);
}
export default Header;
I've included the commented out code of other approaches I've used to get the data I need for the function to work properly so that you can have an Idea of what I've already tried.
The code works fine so long as I only pass values to the function from within the App component. Whenever I click on one of the buttons in the header though, it shows the 'paneName' correctly in the 'switchPaneHandler' function but then in 'LoadPaneHandler' it prints as 'undefined'.
I'm still quite new to React so it's likely a very obvious mistake that I've made but any help is appreciated all the same. Thanks!
I think the key issue here is probably caused by confusion about "What are props? What is state?" - very common when getting started with React.
If we look at the parent component first, you're passing LoadPaneHandler to your Header like it's a callback function. That's not how we do it in React. We need to supply a callback function that takes the name of the pane that we want the parent to show. Naming can really help too in order to make things clearer. Here's how I'd rewrite your parent:
const App = ()=>{
const [currentPaneName, setCurrentPaneName] = React.useState("MainPane")
function updateCurrentPane(newPaneName) {
console.log(`Updating pane from ${currentPaneName} to ${newPaneName}`);
setCurrentPaneName(newPaneName)
}
function LoadPaneHandler(){
console.log(`Showing pane ${currentPaneName}`);
switch(currentPaneName){
case 'MainPane':
return <MainPane />
case 'AddBookPane':
return <AddBookPane />
case 'BookViewPane':
return <BookViewPane />
case 'SignInPane':
return <SignInPane />
default:
return <Pane />
}
}
return(
<React.Fragment>
<Header onNewPaneSelected={updateCurrentPane} />
<main>
<LoadPaneHandler />
</main>
<Footer />
</React.Fragment>
);
}
I've left a sprinkling of console.logs in there so you can get a feel for the timing of re-rendering and responding (React-ing) to change. If you're not familiar with useState(), this is the one React hook you absolutely must understand if you're going to build useful React applications - here are the docs.
Now for the child. You've got a paneName variable but you don't need it. All you're really trying to do is set up the right argument when you call the callback:
const Header=(props)=>{
const switchPaneHandler= (paneName) =>{
event.preventDefault();
console.log(paneName);
props. onNewPaneSelected(paneName);
}
return(
<header id="header">
<div id="header-title">
<h1>Library</h1>
</div>
<div id="header-buttons">
<button onClick={() => switchPaneHandler("BookViewPane")}> View Books</button>
<button onClick={() => switchPaneHandler("AddBookPane")}> Add Books </button>
<button onClick={() => switchPaneHandler("SignInPane")}> Login</button>
</div>
</header>
);
}
Note I changed the name of the callback function it's expecting to be given as a prop - switchPane was a bit misleading - it's not this component's job to switch panes, it just needs to be able to tell someone that the user wants to switch. This also makes it easier to make changes in the future, for example if you have other things that are interested in which pane the user wants to see.
Try to change your child component (Header) like this.
import React from "react";
import "./Header.css";
const Header = (props) => {
const switchPaneHandler = paneName => {
console.log(paneName);
props.switchPane(paneName);
}
return(
<header id="header">
<div id="header-title">
<h1>Library</h1>
</div>
<div id="header-buttons">
<button onClick={()=> switchPaneHandler('BookViewPane')}> View Books</button>
<button onClick={() => switchPaneHandler("AddBookPane")}> Add Books </button>
<button onClick={() => switchPaneHandler("SignInPane")}> Login</button>
</div>
</header>
);
}
In header component your passing the paneName directly, but in parent component you are trying to get as javascript object (props.paneName), that is why you are getting error.
const switchPaneHandler=event=>{
event.preventDefault();
console.log(paneName);
props.switchPane(paneName);
}
so try to access directly,
function LoadPaneHandler(paneName){
var NewPaneName = paneName;
console.log(NewPaneName);
switch(NewPaneName){
case 'MainPane':
return <MainPane />
case 'AddBookPane':
return <AddBookPane />
case 'BookViewPane':
return <BookViewPane />
case 'SignInPane':
return <SignInPane />
default:
return <Pane />
}
}
I have a React component (functional) that contains a child component modifying the state of the parent component. I am using the hook useState for this.
After the state change, there is a "Next" button in the parent component that executes a function referencing the updated state. The problem is this next function uses the old state from before the state was modified by the child component.
I can't use useEffect here as the function needs to execute on the click of the "Next" button and not immediately after the state change. I did some digging about JavaScript closures, but none of the answers address my specific case.
Here's the code
const ParentComponent = () => {
const [myState, setMyState] = useState(0);
const handleNext = () => {
console.log(myState); // prints 0 which is the old value
}
return (
<ChildComponent modifyState = {setMyState} />
<Button onClick={handleNext} > Next </Button>
)
}
export default ParentComponent;
BTW there are no errors.
It's a little difficult to understand without your ChildComponent code. setMyState suggests that you need to update the increase the state by one when you click the next button, but you can't do that without also passing in the state itself.
An easier (imo) solution is to pass a handler down to the child component that is called when the next button is clicked. The handler then sets the state.
const {useState} = React;
function ChildComponent({ handleUpdate }) {
function handleClick() {
handleUpdate();
}
return <button onClick={handleClick}>Click</button>
}
function Example() {
const [myState, setMyState] = useState(0);
function handleUpdate() {
setMyState(myState + 1);
}
function handleNext() {
console.log(myState);
}
return (
<div>
<ChildComponent handleUpdate={handleUpdate} />
<button onClick={handleNext}>Next </button>
</div>
)
}
// Render it
ReactDOM.render(
<Example />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
try to modify like this
<ChildComponent modifyState={(value) => setMyState(value)} />
I've created 2 components C1 and C2 and one custom hook useCounter.
C1 displays "count" property of useCounter hook.
C2 displays "count" property and also increment or decrement it on button click.
Current behavior: When the "count" is changed, the updated value is displayed only in C2 and not in C1.
Expected behavior: Both components should re-render on the "count" update.
Please let me know if I'm missing something.
PS: I've already done this using Context API and redux. Just want to know if the same behavior could be achieved using custom hooks :)
Codesandbox link: Custom hooks demo
import { useState } from "react";
function useCounter() {
const [count, setCount] = useState(0);
const updateCounter = newVal => {
setCount(newVal);
};
//return [count, useCallback( (newVal) => setCount(newVal))];
return [count, updateCounter];
}
export default useCounter;
You might want to use Context API to share the same instance, as the custom hook useCounter will assign a new instance of count on mount:
export const CounterContext = React.createContext();
function App() {
const counterController = useCounter();
return (
<CounterContext.Provider value={counterController}>
<div className="App">
<h1>App Component</h1>
<hr />
<C1 />
<hr />
<C2 />
</div>
</CounterContext.Provider>
);
}
// Use context
function C1() {
const [count] = useContext(CounterContext);
return (
<div>
Component 1 <br />
Count: {count}
</div>
);
}
Additionally, you can use a library like reusable:
const useCounter = createStore(() => {
const [counter, setCounter] = useState(0);
return {
counter,
increment: () => setCounter(prev => prev + 1)
}
});
const Comp1 = () => {
const something = useCounter();
}
const Comp2 = () => {
const something = useCounter(); // same something
}
Hey I am totally agree with #RVRJ's explanations. So what happens when you import any hooks it will create new object of that hook. Let's suppose if you import same hook in two different files that means you are creating two difference object of that hook.
Here I have tried to solve your problem using hooks only, but I am importing hook only once and passed its object to child component <C1 /> and <C2 />.
Here is example how I created single object of useCounter hook
import React from "react";
import C1 from "./components/C1";
import C2 from "./components/C2";
import useCounter from "./hooks/useCounter";
function App() {
const [count, updateCount] = useCounter(); // <<-- created one object
return (
<div className="App">
<h1>App Component</h1>
<hr />
<C1 count={count} updateCount={updateCount} /> {/* passing values ad props */}
<hr />
<C2 count={count} updateCount={updateCount} /> {/* passing values ad props */}
</div>
);
}
export default App;
and now you can access count and updateCount as props in every child.
Here is C1 component after change
// C1 component
import React from "react";
function C1({ count }) { {/* <-- access count as props */}
return (
<div>
Component 1 <br />
Count: {count}
</div>
);
}
export default C1;
And here is your C2 component
// C2 component
import React from "react";
function C3({ count, updateCount }) { {/* <-- access count and as updateCount props */}
const handleIncr = () => {
updateCount(count + 1);
};
const handleDecr = () => {
updateCount(count - 1);
};
return (
<div>
Component 2 <br />
<button onClick={handleIncr}>Increment</button>
<button onClick={handleDecr}>Decrement</button>
<br />
<br />
<br />
Count: {count}
</div>
);
}
export default C3;
Here is updated and working solutions of your problem https://codesandbox.io/s/romantic-fire-b3exw
Note: I don't know what is use case of using hooks for same state values, so I still recommend you should use redux for sharing states between components.
Do two components using the same Hook share state? No. Custom Hooks are a mechanism to reuse stateful logic (such as setting up a subscription and remembering the current value), but every time you use a custom Hook, all state and effects inside of it are fully isolated.
Refer point #3 in Using a Custom Hook
They’re not a way to share state — but a way to share stateful logic. We don’t want to break the top-down data flow!
Reference: Making Sense of React Hooks
in my react's App.js's return i am currently calling this.searchVenues() in a way that works but is messy and i know there is a better way. The searchVenues() function lives in App.js and I have buttons that need to be in their own component, then just <ButtonComponent/> instead of:
render() {
return (
<div className="App container-fluid">
<Navbar/>
<div className="row">
<div className="col-xs-3">
<button onClick ={() => this.searchVenues("yoga+coffee", "5")}>5</button>
<button onClick ={() => this.searchVenues("yoga+coffee", "10")}>10</button>
<button onClick ={() => this.searchVenues("yoga+coffee", "15")}>15</button>
<SideBar {...this.state} handleListItemClick={this.handleListItemClick}/>
</div>
<div className="col-md-9 full-height">
<Map {...this.state}
handleMarkerClick={this.handleMarkerClick}/>
</div>
</div>
<Footer/>
</div>
);
}
but when i do this.searchVenues("yoga+coffee", "5") does not work, understandably so. What's the best or a better way to make it work? How do i access the function from another file ( component )?
I believe you want to declare your searchVenues func in App.js component like this:
searchVenues = (arg1, arg2) => {here is the body of your function}
... and pass it down to the ButtonComponent using props:
<ButtonComponent searchVenues={this.searchVenues}
Once you are in your stateless ButtonComponent, you can create a new function inside it if you want to avoid anonymous functions in your render (you can read on it here)
const searchVenues = (arg1, arg2) => {
return event => {props.searchVenues(arg1, arg2);}
}
... and add it to the onClick event:
<button onClick ={searchVenues('coffee+yoga', props.value)}>{props.value}</button>
If you want your buttons to live in another component, but receive handlers from another component, you can pass them down through props.
import React, { Component } from 'react'
import MyButton from './MyButton'
export default class App extends Component {
buttonClickHandler= () => {
alert('I have been clicked!')
}
render = () => (
<MyButton onClickHandler={this.buttonClickHandler} />
)
}
And here is the MyButton component file:
import React from 'react'
const MyButton = (props) => (
<button onClick={props.onClickHandler}>Click Me for an Alert!</button>
)
export default MyButton
You could either have searchVenues defined in your ButtonComponent or pass it to ButtonComponent as a prop. Then in your ButtonComponent render function you would do the same thing:
<button onClick ={() => this.searchVenues("yoga+coffee", "15")}>15</button>
or if it is passed as a prop
<button onClick ={() => this.props.searchVenues("yoga+coffee", "15")}>15</button>
I will also add that #Bryan is absolutely right about services. Lets say for instance you have a table in a database called Product. Well, you don't want to implement getAllProducts() in every component that needs a list of products. Instead, you would create a class called ProductService where getAllProducts() would be defined. Then, for any component that needs a list of products, you would import the ProductService class and call ProductService.getAllProducts().
Hope that helps.
If you want to execute methods from any component, and the result of those methods will change the global state of the app, you might benefit from using actions.
Whether you're using flux or redux architecture, actions can be triggered from any part of the app, and perform changes in the global state, this changes will be reflected on any component that is listening to this state.
https://reactjs.org/blog/2014/07/30/flux-actions-and-the-dispatcher.html
https://redux.js.org/basics/actions