Good day. I have three components. A Master Component, A TopBar and a Child component (Refers to the rest of my application). The Master is a layout wrapper that nests the Child Component. The Top Bar component is used inside the Master Component so that it is constant througout the site.
The Challenge is that, when a button is clicked on the Top Bar, I want a function to be called inside the Child Component.
What is the best possible way to achieve this
Master.js
import React from 'react'
import TopBar from './TopBar'
const Master = (props) => {
function handleFunc (childFunc) {
setChildFunction(childFunc);
}
return (
<>
<div>
<TopBar onHandle={handleFunc}/>
{props.children}
</div>
</>
)
}
export default Master
TopBar.js
import React from 'react'
const TopBar = ({onHandle}) => {
const childFunc = React.useRef(null)
function handleClick (){
childFunc.current();
onHandle(childFunc);
}
return (
<div>
<button onClick={() => handleClick()}>Click on Me</button> {/* Triggers the alert() function in Child.js */}
</div>
)
}
export default TopBar
Child.js
import React from 'react'
import Master from './Master'
const Child = () => {
function alert(){
console.log("This function is called when thew Top Bar Button is clicked");
}
return (
<div>
<Master>
Child Contents
</Master>
</div>
)
}
export default Child
What is the best way to achieve this.
I initially wanted to pass the ref object to the Master component so it can be passed down to the Child as shown above, but I keep getting errors (that "childFunc" is not a function).
Thanks in advance
You can pass your Child function to the Master component, then from the Master Component to TopBar component, that will cause props drilling but it will solve your problem,
Child.js
import React from 'react'
import Master from './Master'
const Child = () => {
function alert(){
console.log("This function is called when thew Top Bar Button is clicked");
}
return (
<div>
<Master func={alert}>
Child Contents
</Master>
</div>
)
}
export default Child
Master.js
import TopBar from './TopBar'
const Master = ({func}) => {
return (
<>
<div>
<TopBar func={func}/>
{props.children}
</div>
</>
)
}
export default Master
TopBar.js
import React from 'react'
const TopBar = ({func}) => {
return (
<div>
<button onClick={() => func()}>Click on Me</button>
</div>
)
}
export default TopBar
Related
I want to know I have one parent component and two child components and these child components are separated according to the user role. I have passed the parent state in these child components. In the beginning, both child components have the same state value, but if I update the state value in one child component, it will not update the state value in another component why.
Here is an example code.
import React, { useEffect, useState } from "react";
import Demo1 from "./Demo1";
import Demo2 from "./Demo2";
const Demo = () => {
const [staVal, setStaVal] = useState("hi");
console.log(staVal);
const user = JSON.parse(localStorage.getItem("auth"));
return (
<div>
{user.role === "user" ? (
<Demo1 staVal={staVal} handler={() => setStaVal("google")} />
) : (
<Demo2 staVal={staVal} />
)}
</div>
);
};
export default Demo;
Demo1 component:
import React from "react";
const Demo1 = ({ staVal, setStaVal, handler }) => {
return (
<>
<div>demo1:{staVal}</div>
<button onClick={handler}>clik</button>
</>
);
};
export default Demo1;
Demo 2 component:
import React from "react";
const Demo2 = ({ staVal }) => {
return <div>demo2:{staVal}</div>;
};
export default Demo2;
Accessing localStorage is a side effect.
Side effects cannot be called from the render method (for Class components) or the top level (function components).
In your code, access the localStorage inside useEffect(()=>{}, []) or
inside componentDidMount if you want to make it a class component.
use the useEffect to get the item from the local storage.
const [user,setUser]=useState(null);
useEffect(()=>{
const currentUser = JSON.parse(localStorage.getItem("auth"));
setUser(currentUser)
},[])
return (
<div>
{user.role === "user" ? (
<Demo1 staVal={staVal} handler={() => setStaVal("google")} />
) : (
<Demo2 staVal={staVal} />
)}
</div>
);
};
Parent Component is like below
import React, { useState } from "react";
import EntityDefinition from "./EntityDefinition";
export default function EntitySelection(props) {
const testFun = () => {
console.log("Function activated");
};
return (
<>
<div>
<EntityDefinition
testFun={testFun}
/>{/**Calling a Class based Component*/}
</div>
</>
);
}
Class based Component (Child)
import React from "react";
import { ComboBox, DropdownOption, Button } from "react-widgets";
import axios from "axios";
export default class EntityDefinition extends React.Component {
render() {
return (
<React.Fragment>
<div>
{" "}
<Button onClick={this.testFun}>Close</Button>{" "} {/*/Calling the function passed*/}
</div>
</React.Fragment>
);
}
}
but when i clicked the button the testFun function is not being called.
i tried something like onClick={this.state.testFun} , onClick={testFun}
but nothing is happening.
can someone point what am i doing wrong here.
testFun is a prop. So use this.props.testFun
onClick={this.props.testFun}
testFun is a prop, and you are using the ES6 class component, and it receives the props as a props object, so you can try accessing it as below
onClick={this.props.testFun}
You need to refer to props passed to a class based components using this.props from inside the class based components:
Docs
In your case, you should change the onClick listener to this:
<Button onClick={this.props.testFun}>
I am trying to trigger a start function in a different componentB when I click the start button in componentA
Note: Both components are neither parent to child components
Component A
import React from "react"
function ComponentA(props) {
return (
<div>
<button>Start</button>
</div>
)
}
export default ComponentA;
Component B
import React from "react";
function ComponentB(props) {
const [isStarted, setStarted] = React.useState(false);
const start = () => setStarted(true);
return <div>{isStarted ? "Starting..." : "Not Starting.."}</div>;
}
export default ComponentB;
One way you could do it is by creating a callback prop on ComponentA, changing the state of the parent component of ComponentA and passing it to ComponentB via a prop and capture that prop change with a useEffect.
Example:
Parent
function Parent(){
const [started, setStarted] = useState(false)
return(
<div>
<ComponentA onClick={() => setStarted(true)}/>
<ComponentB started={started}/>
</div>
)
}
ComponentA
function ComponentA({onClick}){
return(
<div>
<button onClick={() => onClick()}/>
</div>
)
}
ComponentB
function ComponentB({started}) {
const [isStarted, setStarted] = React.useState(started);
useEffect(() => {
setStarted(started)
}, [started])
return <div>{isStarted ? "Starting..." : "Not Starting.."}</div>;
}
Another way would be using useContext:
https://reactjs.org/docs/hooks-reference.html#usecontext
https://reactjs.org/docs/context.html
Honestly, I am a bit lazy to also include an example which is in my opinion worse. Here is an example that uses useContext that might be useful.
https://stackoverflow.com/a/54738889/7491597
What I want:
I'm trying to add a dynamic theme option to a react-styleguidist project I'm working on. Following the idea laid out in this unfinished and closed pr, I added a custom ThemeSwitcher component, which is a select menu that is rendered in the table of contents sidebar. Selecting an option should update the brand context, which renders the corresponding theme using styled-components' BrandProvider. It should function like the demo included with the closed pr: https://fancy-sg.surge.sh/.
What's not working:
I can't access the same context in my ThemedWrapper as is provided and updated in the StyleguideWrapper and ThemeSwitcher. Examining the tree in the React Components console, it looks like react-styleguidist may render ReactExample outside of the StyleguideRenderer, which means it loses the context from the provider in that component.
Assuming I'm correct about the context not updating in ThemedWrapper due to it being located outside of StyleGuideRenderer, two high level ideas I have (but haven't been able to figure out how to do) are:
Find the correct component that is an ancestor of both StyleGuideRenderer and ReactExample in the react-styleguidist library and add the BrandProvider there so that ThemedWrapper now has context access
Some other context configuration that I haven't found yet that will allow two components to consume the same context without having a provider as an ancestor (is this possible??)
What I have:
Here are the condensed versions of the relevant code I'm using.
brand-context.js (exports context and provider, inspired by Kent C Dodds
import React, { createContext, useState, useContext } from 'react';
const BrandStateContext = createContext();
const BrandSetContext = createContext();
function BrandProvider({ children, theme }) {
const [brand, setBrand] = useState(theme);
return (
<BrandStateContext.Provider value={brand}>
<BrandSetContext.Provider value={(val) => setBrand(val)}>
{children}
</BrandSetContext.Provider>
</BrandStateContext.Provider>
);
}
function useBrandState() {
return useContext(BrandStateContext);
}
function useBrandSet() {
return useContext(BrandSetContext);
}
export { BrandProvider, useBrandState, useBrandSet };
StyleGuideWrapper.jsx (Copy of rsg-components/StyleguideRenderer, with addition of ThemeSwitcher component to toggle theme from ui; passed in styleguide config as StyleGuideRenderer)
import React from 'react';
import cx from 'clsx';
import Styled from 'rsg-components/Styled';
import ThemeSwitcher from './ThemeSwitcher';
import { BrandProvider } from './brand-context';
export function StyleGuideRenderer({ children, classes, hasSidebar, toc }) {
return (
<BrandProvider>
<div className={cx(classes.root, hasSidebar && classes.hasSidebar)}>
<main className={classes.content}>
{children}
</main>
{hasSidebar && (
<div className={classes.sidebar} data-testid="sidebar">
<section className={classes.sidebarSection}>
<ThemeSwitcher classes={classes} />
</section>
{toc}
</div>
)}
</div>
</BrandProvider>
);
}
StyleGuideRenderer.propTypes = propTypes;
export default Styled(styles)(StyleGuideRenderer);
ThemeSwitcher.jsx
import React from 'react';
import Styled from 'rsg-components/Styled';
import { useBrandSet, useBrandState } from './brand-context';
const ThemeSwitcher = ({ classes }) => {
const brand = useBrandState();
const setBrand = useBrandSet();
const onBrandChange = (e) => setBrand(e.target.value);
const brands = ['foo', 'bar'];
return (
<label className={classes.root}>
Brand
<select value={brand} onChange={onBrandChange}>
{brands.map((brand) => (
<option key={brand} value={brand}>{brand}</option>
))}
</select>
</label>
);
};
export default Styled(styles)(ThemeSwitcher);
ThemedWrapper.jsx (passed in styleguide config as Wrapper, and wraps each example component to provide them to styled-components)
import React from 'react';
import { ThemeProvider } from 'styled-components';
import { BrandStateContext } from './brand-context';
const LibraryProvider = ({ brand, children }) => {
return (
<ThemeProvider theme={brand}>{children}</ThemeProvider>
);
};
function ThemedWrapper({ children }) {
return (
<BrandStateContext.Consumer>
{brand => (
<LibraryProvider brand={brand}>{children}</LibraryProvider>
)}
</BrandStateContext.Consumer>
);
}
export default ThemedWrapper;
I'm having a problem with my React component. The nested children of my component ControlPanel don't seem to be rendering. Here is my code:
class App extends Component {
render() {
return (
<div className="App">
<ControlPanel>
<CustomerDisplay />
</ControlPanel>
</div>
);
}
}
I have the following two lines at the top of this file:
import ControlPanel from './components/control_panel';
import CustomerDisplay from './components/customer_display';
And here is my ControlPanel Component:
import React from 'react';
import CSSModules from 'react-css-modules';
import styles from './styles.scss';
const ControlPanel = () => {
return (
<div className="control_panel" id="control_panel">
</div>
);
}
export default CSSModules(ControlPanel, styles);
I have tried:
Calling the component as a full HTML tag (opening & closing)
Nesting the CustomerDisplay component in the ControlPanel component (in the ControlPanel's index.jsx file)
I know that nesting component's is possible. I've seen it done. For some reason it just won't work for me.
To allow components to contain children and render them correctly, you have to use this.props.children. This is passed to all components with children as a prop and contains the children of the component, as explained by the React documentation:
Containment
Some components don't know their children ahead of time. This is especially common for components like Sidebar or Dialog that represent generic "boxes".
We recommend that such components use the special children prop to pass children elements directly into their output:
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}
This lets other components pass arbitrary children to them by nesting the JSX
function WelcomeDialog() {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
Welcome
</h1>
<p className="Dialog-message">
Thank you for visiting our spacecraft!
</p>
</FancyBorder>
);
}
As described in the documentation, some components don't know their children ahead of time -- they may be generic wrappers or boxes of content that vary, which is what your ControlPanel is. So, to render the children of your component, you must render the children from the children prop explicitly in the parent's render method. Thus, apply it like this to your code:
const ControlPanel = (props) => {
return (
<div className="control_panel" id="control_panel">
{props.children}
</div>
);
}
Notice how props.children is rendered (not this.props.children because it is a function component).
You can access the nested elements through props. So in your case do this:
const ControlPanel = (props) => {
return (
<div className="control_panel" id="control_panel">
{props.children}
</div>
);
}
You need to render the children in ControlPanel
const ControlPanel = ({ children }) => {
return (
<div className="control_panel" id="control_panel">
{children}
</div>
);
}
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}
export default function WelcomeDialog() {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
Welcome
</h1>
<p className="Dialog-message">
Thank you for visiting our spacecraft!
</p>
</FancyBorder>
);
}
Anything inside the<FancyBorder>JSX tag gets passed into the FancyBorder component as childrenprop. Since FancyBorder renders {props.children} inside a <div>, the passed elements appear in the final output.
This is what I was looking after, check it out here
https://reactjs.org/docs/composition-vs-inheritance.html
Your App.js (I understand that it is your JSX Index) should be:
import React from 'react';
import ReactDOM from 'react-dom';
export default class App extends Component {
render() {
return (
<div className="App">
<ControlPanel>
<CustomerDisplay />
</ControlPanel>
</div>
);
}
}
ReactDOM.render(<App/>, document.getElementById('YOUR_ROOT_ID'));
Try to add export default before class (in all your components).