Pass context between siblings using context in React - reactjs

I have the following code where I am trying to get a value from one component to its sibling using the context api.
import React from "react";
import ReactDOM from "react-dom";
function App() {
return (
<div>
<TheButton />
<Display />
</div>
);
}
export const NumberContext = React.createContext();
function TheButton() {
return (
<NumberContext.Provider value={"test"}>
<button>Click me</button>
</NumberContext.Provider>
);
}
function Display() {
const context = React.useContext(NumberContext);
return <div>The answer {context}.</div>;
}
ReactDOM.render(<App />, document.querySelector("#root"));
As you can see I am passing the 'test' value in the provider, but when the page renders all I see is "The answer ."
Here is a a codesandbox for the issue https://codesandbox.io/s/pedantic-forest-zjlc2

Despite the fact that context gives you a decoupled way of passing props the Provider still must be on a higher hierarchy. Provide your context from your App and consume it from children.
export const NumberContext = React.createContext();
function App() {
const [foo, changeFoo] = useState('foo')
return (
<NumberContext.Provider value={{ foo, changeFoo }}>
<TheButton />
<Display />
</NumberContext.Provider>
);
}
function TheButton() {
const { changeFoo } = useContext(NumberContext)
return (
<button onClick={() => changeFoo('bar')}>Click me</button>
);
}
function Display() {
const context = React.useContext(NumberContext);
return <div>The answer {context.foo}.</div>;
}

Related

How to pass particular property to all child components in react?

I am absolute beginner in react. I have to pass particular property to all the components inside the div without passing them into individual,
For Example, Instead of doing this:
function App() {
return (
<div>
<Component1 props = {propObject}/>
<Component2 props = {propObject}/>
<Component3 props = {propObject}/>
</div>
);
}
How can I achieve something like this? :
function App() {
return (
<div props={propObject}>
<Component1 />
<Component2 />
<Component3 />
</div>
);
}
React Context
provides a way to share values to child components without having to explicitly pass a prop through every level of the tree.
You need to import React.context from react
syntax:
// context.js
import React, { createContext } from "react"
export const MyContext = React.createContext(null);
Now You need to create a provider of this context, to pass the current context value to the tree below. So you need to wrap all of your child component by the React.Provider.
syntax:
// MainComponent.js
import { MyContext } from "./context"
import { ChildComponent1, ChildComponent2, ChildComponent3 } from "./childcomponent"
const MainComponent = () => {
const myObject = {name: "John"}
return (
<MyContext.Provider props={myObject}>
<ChildComponent1 />
<ChildComponent2 />
<ChildComponent3 />
</MyContext.Provider>
)
}
Now all the child component of this MainComponent has access of this props value which is provided by MyContext.Provider. Now you can simply get that values to your child components.
syntax:
// childcomponent.js
import React, { useContext } from "react"
import { MyContext } from "./context"
export const ChilcComponent1 = () => {
const props = useContext(MyContext)
return (
<p>
My name is: {props?.key}
</p>
)
}
You can use from ThemeProvider:
function App() {
return (
<ThemeProvider props = {propObject}>
<Component1 />
<Component2 />
<Component3 />
</ThemeProvider >
);
}

how can I call a method that is inside of Component? (I can't pass this method in props)

this seems like a duplicate question but it is not, the examples I have seen explain how to pass a function through props.
I have three components: ComponentA, ComponentB and App (App is the component root). I want to call a function contains in ComponentA using a button that is inside of ComponentB.
import "./styles.css";
import ComponentA from "./componentA";
import ComponentB from "./componentB";
export default function App() {
return (
<div className="App">
<>
<ComponentA>
<ComponentB />
</ComponentA>
</>
</div>
);
}
const ComponentA = ({ children }) => {
const hello = () => {
alert("hello");
//In my real scenario, this method contains a big logic...
};
return (
<>
Component A<div>{children}</div>
</>
);
};
const ComponentB = () => {
const callComponentAFunction = () => {
// I need call "hello()" from ComponentA
};
return (
<>
<button onClick={callComponentAFunction}>
Call function from ComponentA
</button>
</>
);
};
How can I call hello() (function inside of ComponentA) from ComponentB?
this is my live code
You can achieve that in many ways. Pass that function as props from component A.
Working example Modified code
//App.js
import "./styles.css";
import ComponentA from "./componentA";
// import ComponentB from "./componentB";
export default function App() {
return (
<div className="App">
<>
<ComponentA />
</>
</div>
);
}
//Component A
import ComponentB from "./componentB";
const ComponentA = ({ children }) => {
const hello = () => {
alert("hello");
};
return (
<>
Component A<div>{children}</div>
<ComponentB hello={hello} />
</>
);
};
export default ComponentA;
//Component B
const ComponentB = ({ hello }) => {
return (
<>
<button onClick={hello}>Call function from ComponentA</button>
</>
);
};
export default ComponentB;
you can also use the React.Children.map, the example like this: https://codesandbox.io/s/hardcore-ellis-ksgkd?file=/src/componentB.js

React complains element type is invalid when trying to use context

I'm trying to use React Context to update navbar title dynamically from other child components. I created NavbarContext.js as follows. I have wrapped AdminLayout with NavContext.Provider and use useContext in Course.js to dynamically update navbar title inside useEffect. However, when I'm doing this, react throws the following error on the screen.
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
How can I use context properly so that I can update Header title from Course.js inside its useEffect?
NavbarContext.js
import React, {useState} from 'react'
export default () => {
const [name,setName] = useState("")
const NavContext = React.createContext({
name: "",
changeName: name => setName(name)
})
const NavProvider = NavContext.Provider
const NavConsumer = NavContext.Consumer
return NavContext
}
AdminLayout.js
<NavContext.Provider>
<div className={classes.wrapper}>
<Sidebar
routes={routes}
logoText={"Widubima"}
logo={logo}
image={image}
handleDrawerToggle={handleDrawerToggle}
open={mobileOpen}
color={color}
{...rest}
/>
<div className={classes.mainPanel} ref={mainPanel}>
<Navbar
routes={routes}
handleDrawerToggle={handleDrawerToggle}
{...rest}
/>
{/* On the /maps route we want the map to be on full screen - this is not possible if the content and conatiner classes are present because they have some paddings which would make the map smaller */}
{getRoute() ? (
<div className={classes.content}>
<div className={classes.container}>{switchRoutes}</div>
</div>
) : (
<div className={classes.map}>{switchRoutes}</div>
)}
</div>
</div>
</NavContext.Provider>
Navbar.js
import NavContext from "context/NavbarContext"
export default function Header(props) {
function makeBrand() {
var name;
props.routes.map(prop => {
if (window.location.href.indexOf(prop.layout + prop.path) !== -1) {
name = prop.name;
document.title = name;
}
return null;
});
return name;
}
return (
<AppBar className={classes.appBar + appBarClasses}>
<Toolbar className={classes.container}>
<div className={classes.flex}>
{/* Here we create navbar brand, based on route name */}
<NavContext.Consumer>
{({ name, setName }) => (
<Button
color="transparent"
href="#"
className={classes.title}
style={{ fontSize: "1.5em", marginLeft: "-2%" }}
>
{makeBrand() || name}
</Button>
)}
</NavContext.Consumer>
</Toolbar>
</AppBar>
);
}
Course.js
import React, { useState, useEffect, useContext } from "react";
import NavContext from "context/NavbarContext"
const AdminCourse = props => {
const context = useContext(NavContext);
useEffect(() => {
Axios.get('/courses/'+props.match.params.courseId).then(
res => {
context.changeName("hello")
}
).catch(err => {
console.log(err)
})
return () => {
setCourseId("");
};
});
return (
<GridContainer>
</GridContainer>
);
};
export default AdminCourse;
i think problem is there with your NavbarContext.js.
you are not exporting NavContext also.
you are defining provider, consumer but you are not using them either.
here's how you can solve your problem.
first create context and it's provider in a file as following.
NavContext.js
import React, { useState } from "react";
const NavContext = React.createContext();
const NavProvider = props => {
const [name, setName] = useState("");
let hookObject = {
name: name,
changeName: setName
};
return (
<NavContext.Provider value={hookObject}>
{props.children}
</NavContext.Provider>
);
};
export { NavProvider, NavContext };
in above code first i am creating context with empty value.
the i am creating NavProvider which actually contains value name as a state hook inside it.hookObject exposes state as per your naming conventions in code.
now i for testing purpose i defined two consumers.
one is where we update name in useEffect, that is ,
ConsumerThatUpdates.js
import React, { useContext, useEffect } from "react";
import { NavContext } from "./NavContext";
const ConsumerThatUpdates = () => {
const { changeName } = useContext(NavContext);
useEffect(() => {
changeName("NEW NAME");
}, [changeName]);
return <div>i update on my useeffect</div>;
};
export default ConsumerThatUpdates;
you can update useEffect as per your needs.
another is where we use the name,
ConsumerThatDisplays.js
import React, { useContext } from "react";
import { NavContext } from "./NavContext";
const ConsumerThatDisplays = () => {
const { name } = useContext(NavContext);
return <div>{name}</div>;
};
export default ConsumerThatDisplays;
and finally my App.js looks like this,
App.js
import React from "react";
import "./styles.css";
import { NavProvider } from "./NavContext";
import ConsumerThatDisplays from "./ConsumerThatDisplays";
import ConsumerThatUpdates from "./ConsumerThatUpdates";
export default function App() {
return (
<div className="App">
<NavProvider>
<ConsumerThatDisplays />
<ConsumerThatUpdates />
</NavProvider>
</div>
);
}
hope this helps!!
if you want to know more about how to use context effectively, i recooHow to use React Context effectively

HOC's and Render Props With Functional Components in React 16

I'm somewhat confused on the relationship between functional components in React and the Render Props and HOC patterns.
That is,
is it true that the only way to create Render Prop is with a class component?
is it true that the only way to create an HOC is with a class component?
And the same for usage.
I'm trying to find examples of Render Props and HOC's with functional components and all I find are class components. I get that React Hooks do a lot of the same, but I'm trying to understand how the Render Props and HOC patterns can apply to functional components (or if they do at all)
Edit Below:
Applying what #chaimFriedman suggested, this is what I came up with using no class or component for an HOC hoping it makes sense.
import React, { useState, useEffect } from 'react';
import useAxios from 'axios-hooks';
function withFetching(url) {
return function(Speakers) {
return () => {
const [speakerData, setSpeakerData] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [{ data, loading }, refetch] = useAxios('http://localhost:4000/speakers');
useEffect(() => {
setSpeakerData(data);
setIsLoading(loading);
}, [loading]);
if (isLoading) return <div>loading..</div>;
return <Speakers data={speakerData}></Speakers>;
};
};
}
const Speakers = function(props) {
//debugger;
return (
<ul>
{props.data.map((speaker) => (
<li key={speaker.id}>
<span>
{speaker.firstName} {speaker.lastName}
</span>
</li>
))}
</ul>
);
};
const API = 'http://localhost:4000/speakers';
export default withFetching(API)(Speakers);
Both render props and HOC can absolutely apply to functional components. Let's think a little more about what each one really is to see why they do in fact work with functions as well as classes.
Render props is when you have a prop that is a function which returns JSX. This of course should work for function components because aside from life cycle methods there really isnt much that is different than class components. Here is an example with code.
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function Renderer(props) {
return (
props.children()
);
}
function App() {
return (
<div className="App">
<Renderer>
{() => {
return (
<h1>I am being rendered by Renderer</h1>
);
}}
</Renderer>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Now for HOC.
A HOC really is just a higher order function, but because we use it in react we call it a higher order component. A higher order function is a function which either accepts a function as an argument, or returns a function. Now a functional component can absolutely do this. Here is an example.
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function Renderer(Wrapped) {
return function New(props) {
return <Wrapped {...props} />
}
}
function Child(props) {
return (
<h1>Hello {props.name}</h1>
);
}
function App() {
const C = Renderer(Child)
return (
<div className="App">
<C name="john" />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
EDIT: I realized my HOC example was wrong so updated.
I hope this helps.
Here is the sample code converting example from React documentation into function component. https://reactjs.org/docs/render-props.html
import React from "react";
const Cat = ({mouse}) => {
return (
<img
src="/cat.png"
alt="cat"
style={{ position: "absolute", left: mouse.x, top: mouse.y }}
/>
);
};
const Mouse = (props) => {
const [state, setState] = React.useState();
const handleMouseMove = (event) => {
setState({
x: event.clientX,
y: event.clientY
});
};
return (
<div style={{ height: "100vh" }} onMouseMove={handleMouseMove}>
{props.render(state)}
</div>
);
};
const MouseTracker = () => {
return (
<div>
<h1>Move the mouse around!</h1>
<Mouse render={(mouse) => <Cat mouse={mouse} />} />
</div>
);
};
export const App = () => {
return (
<div className="App">
<MouseTracker />
</div>
);
}

Pass input data from component A to component B using React context

My request is very simple:
Could you please provide me with an example where an input data is passed from component A to component B using context API.
Requirements: there should be an input value entered in component A. We send the input value over to component B using context.
A and B are sibling components.
You can do this way.
Make a function in Context.js state which set the state of your input field.
//context.js state
state = {
inputFieldName: null,
setInputField: () => {
this.setState() //set value for inputFieldName here
}
}
Call that setInputField function on onChange in component A using Context and you can get inputFieldName state from Context in component B.
The required properties of the context:
1. A string property which stores the user's input
2. A method which updates the user's input.
And in your case the component A produces the input and calls the method to update it to context. The component B consumes the changes of the input from the context. it ends up with such a prototype.
import React, { useContext, useState } from "react";
import ReactDOM from "react-dom";
const MyContext = React.createContext(null);
function A() {
const { onChange } = useContext(MyContext);
const [input, setInput] = useState(null);
return (
<input
type="text"
value={input}
onChange={e => {
setInput(e.target.value);
onChange(e.target.value);
}}
/>
);
}
function B() {
const { input } = useContext(MyContext);
return <div>{input}</div>;
}
function App() {
const [input, setInput] = useState(null);
return (
<MyContext.Provider value={{ input, onChange: setInput }}>
<div>
<A />
<B />
</div>
</MyContext.Provider>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Here is my try, hope it will help you
componentA.jsx
import React from "react";
const ComponentA = props => {
return (
<div>
<h1>{`ComponentA: ${props.data}`}</h1>;
<button onClick={() => props.onValueChange("value changed by ComponentA")}>
click
</button>
</div>
);
};
export default ComponentA;
componentB.jsx
import React from "react";
const ComponentB = props => {
return <h1>{`ComponentB: ${props.data}`}</h1>;
};
export default ComponentB;
App.js
import React, { Component } from "react";
import ComponentB from "./componentA";
import ComponentA from "./componentB";
class App extends Component {
state = {
data: "common value from parent"
};
handleChange = input => {
this.setState({ data: input });
};
render() {
return (
<div>
<ComponentB data={this.state.data}></ComponentB>
<ComponentA
data={this.state.data}
onValueChange={this.handleChange}
></ComponentA>
</div>
)}
}

Resources