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

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>
)}
}

Related

React component is not rendering after state change with button click while using useContext hook

I am using useContext hook for the first time as I wanted the re-rendering of one component by click of a button component. Here's my code:
QuestionContext.js (for creating context):
import { createContext } from "react";
const QuestionContext = createContext()
export default QuestionContext
SectionState.js (for providing value to children):
import {React, useState} from 'react'
import QuestionContext from './QuestionContext'
import questions from '../data/questions.json'
const SectionState = (props) => {
// set questions from json to an array of 4 elements
const newQuestions = Object.keys(questions.content).map(key => questions.content[key].question)
const newState = {
"qID": 0,
"questionTxt": newQuestions[0],
}
//useState for Question state
const [currentQuestion, setCurrentQuestion] = useState(0)
const [questionCtx, setQuestionCtx] = useState(newState)
const updateQuestion = () => {
if(currentQuestion > newQuestions.length) {
console.log("no more questions")
}
else{
setCurrentQuestion(currentQuestion + 1)
setQuestionCtx(() => ({
"qID": currentQuestion,
"questionTxt": newQuestions[currentQuestion]
}))
}
}
return (
<QuestionContext.Provider value = {{newState, updateQuestion}}>
{props.children}
</QuestionContext.Provider>
)
}
export default SectionState
The following two components are child of <SectionState /> component
Buttons.js:
import React, { useContext } from 'react'
import QuestionContext from '../context/QuestionContext'
const Buttons = () => {
const example = useContext(QuestionContext)
const clickHandler = () => {
example.updateQuestion()
}
return (
<div className='flex flex-row justify-between'>
{/* <button className='btn backdrop-blur-md bg-slate-600 rounded-full xl:w-48 md:w-44 text-slate-50' onClick={ clickHandler }>Prev</button> */}
<button className='btn btn-accent rounded-full xl:w-48 md:w-44' onClick={ clickHandler }>Next</button>
</div>
)
}
export default Buttons
Questions.js
import { React, useContext } from 'react'
import './styles/Questions.css'
import QuestionContext from '../context/QuestionContext'
const Questions = () => {
const newContext = useContext(QuestionContext)
return (
<>
<h1 className='text-4xl text-zinc-50'>{ newContext.newState.questionTxt }</h1>
</>
)
}
export default Questions
Every time I have clicked on the button, I could check in the console that newState state has changed, but this new state won't render in <Questions /> component. I could still see newContext.newState.questionTxt holding the initial value i.e. newQuestions[0]. What am I doing wrong here?
Here's a reproduced link in code sandbox
<QuestionContext.Provider value = {{newState, updateQuestion}}
Here you passed newState and updateQuestion as a value of context. In Button component you update currentQuestion and questionCtx using updateQuestion() but in Questions component, you are using the value of newState as
const newContext = useContext(QuestionContext)
<h1 className='text-4xl text-zinc-50'>{ newContext.newState.questionTxt }</h1>
Here newState is not a state. It is just a variable and it is not updated at all so you don't get an updated value in Question component.
Solution:
So I think you should pass the questionCtx as a value of context Provider like
<QuestionContext.Provider value = {{questionCtx , updateQuestion}}
Use it like
<h1 className='text-4xl text-zinc-50'>{ newContext.questionCtx.questionTxt }</h1>
Working Codesandbox link: https://codesandbox.io/s/react-usecontext-forked-frgtw1?file=/src/context/SectionState.js

show - hide component with hook function works only one time

I am trying to show and hide a functional component, it's works only works on load. after hide it's not shows again. i understand that, the way i use the functional component in wrong way.
any one suggest me the correct way please?
here is my code : (index.tsx)
import React, { Component, useState } from 'react';
import { render } from 'react-dom';
import Hello from './Hello';
import './style.css';
const App = () => {
const [isBoolean, setBoolean] = useState(false);
const showComponent = () => {
setBoolean(true);
};
return (
<div>
<Hello isBoolean={isBoolean} />
<p>Start editing to see some magic happen :)</p>
<button onClick={showComponent}>Show hello component</button>
</div>
);
};
render(<App />, document.getElementById('root'));
Hello component:
import React, { useEffect, useState } from 'react';
export default ({ isBoolean }: { isBoolean: boolean }) => {
const [isShow, setIsShow] = useState(false);
useEffect(() => {
setIsShow(isBoolean);
}, [isBoolean, setIsShow]);
const shufler = () => {
setIsShow(false);
};
if (!isShow) {
return null;
}
return (
<div>
<p>hi {JSON.stringify(isShow)}</p>
<button onClick={shufler}>Hide Component</button>
</div>
);
};
Live Demo
To explain why your code isn't working:
useEffect(() => {
setIsShow(isBoolean);
}, [isBoolean, setIsShow]);
initially when you set isBoolean to true in parent, this useEffect in child runs too
Then you set isShow to false from the child component
Then again you set isBoolean to true in the parent component, but for the useEffect above, the isBoolean is true now, and it was true also in previous render, so it doesn't run anymore.
So if possible, no need to duplicate isBoolean state also in child, just pass it as props and use it directly, as in the other answer.
No need to maintain a derived state from prop in child component(Hello), you can pass callback and state as props from parent component(index) to child.
Cause of the Problem:
After hiding the component isShow was set to false , isBoolean is still true. So the next time when we click the show button isBoolean hasn't changed, it's still true which will not trigger the useEffect in the Hello.tsx , isShow was never set to true which causes the child to return null.
index.tsx
import React, { Component, useState } from 'react';
import { render } from 'react-dom';
import Hello from './Hello';
import './style.css';
const App = () => {
const [isBoolean, setBoolean] = useState(false);
const showComponent = () => {
setBoolean(true);
};
const hideComponent = () => {
setBoolean(false);
}
return (
<div>
<Hello isBoolean={isBoolean} hideComponent={hideComponent} />
<p>Start editing to see some magic happen :)</p>
<button onClick={showComponent}>Show hello component</button>
</div>
);
};
render(<App />, document.getElementById('root'));
Hello.tsx
import React, { useEffect, useState } from 'react';
export default ({ isBoolean, hideComponent }: { isBoolean: boolean }) => {
if (!isBoolean) {
return null;
}
return (
<div>
<p>hi {JSON.stringify(isBoolean)}</p>
<button onClick={hideComponent}>Hide Component</button>
</div>
);
};

check state of another functional component

i am new to react, i want to call the state of an outside function, for example :
export default function Child() {
const [succeeded, setSucceeded] = useState(false);
}
export default function Parent() {
if(Child.succeeded){
// do the following
}
}
i know that props are used for const objects only, and i don't want to merge both functions in a signle one to keep things organised, i would like to check for child's state to do the next step, or to callback the parent function with the new state to notify it. is there any way to do it ? Thanks a lot for your time.
Another approach is that you can use the useRef, which is very handy in some cases.
import React, {useState} from "react";
export default function Child({nameRef}) {
const [name, setName] = useState('');
React.useEffect(() => {
nameRef.current = name;
}, [name]);
return (
<>
<input nameRef={nameRef} type="text" onChange={event => setName(event.target.value)} />
</>
);
}
import React, { useState, useRef } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
import Child from './Child';
function App() {
let [name, setName] = useState("Nate");
let nameRef = useRef();
const submitButton = () => {
console.log(nameRef.current);
};
return (
<div className="App">
<p>{name}</p>
<div>
<Child nameRef={nameRef} />
<button type="button" onClick={submitButton}>
Submit
</button>
</div>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

React useState() & useContext() - TypeError: setState is not a function - What am I doing wrong?

I've got a few React functional Components that I would like to share a state. In this example two toggle buttons that would conditionally show/hide a searchbar and a navbar.
--Solution, based on the accepted answer, on the bottom--
I'm completely new to useContext() and I keep running into the following error in the console:
Uncaught TypeError: setSearchbarToggle is not a function This goes for both buttons.
Bellow I have a filtered example code. It is just for the example I use the states in one file. In real life I would re-use the states in multiple functional components.
This is my header.js
import React, { useState, useContext } from "react"
import "./header.sass"
import { Context } from "./HeaderContext"
export const Header = () => {
const headerContext = useContext(Context)
const { navbarToggle, setNavbarToggle, searchbarToggle, setSearchbarToggle } = headerContext
return (
<React.Fragment>
<div className={"sticky-top"}>
<button onClick={ () => setNavbarToggle( !navbarToggle )}> Toggle Menu </button>
<button onClick={ () => setSearchbarToggle( !searchbarToggle )}> Toggle Search </button>
{navbarToggle && <h3>Menu is showing</h3>}
{searchbarToggle && <h3>Searchbar is showing</h3>}
</div>
</React.Fragment>
)
}
export default Header
And this is my HeaderContext.jsx
import React, { createContext, useState } from "react";
import PropTypes from "prop-types";
export const Context = createContext({});
export const Provider = props => {
const {
navbarToggle: initialNavBarToggle,
searchbarToggle: initialSarchbarToggle,
children
} = props;
const [navbarToggle, setNavbarToggle] = useState(initialNavBarToggle);
const [searchbarToggle, setSearchbarToggle] = useState(initialSarchbarToggle);
const headerContext = {
navbarToggle, setNavbarToggle,
searchbarToggle, setSearchbarToggle
};
return <Context.Provider value={headerContext}>{children}</Context.Provider>;
};
export const { Consumer } = Context;
Provider.propTypes = {
navbarToggle: PropTypes.bool,
searchbarToggle: PropTypes.bool
};
Provider.defaultProps = {
navbarToggle: false,
searchbarToggle: false
};
I hope you can shed some light on this for me
--edit--
This is my code based on the accepted answer.
import React, { useContext } from "react"
import { Provider,Context } from "./HeaderContext"
export const HeaderWithContext= () => {
const headerContext = useContext(Context)
const { navbarToggle, setNavbarToggle, searchbarToggle, setSearchbarToggle } = headerContext
return (
<React.Fragment>
<div className={"sticky-top"}>
<button onClick={ () => setNavbarToggle( !navbarToggle )}> Toggle Menu </button>
<button onClick={ () => setSearchbarToggle( !searchbarToggle )}> Toggle Search </button>
{navbarToggle && <h3>Menu is showing</h3>}
{searchbarToggle && <h3>Searchbar is showing</h3>}
</div>
</React.Fragment>
)
}
export const Header = () => {
return (
<Provider>
<HeaderWithContext/>
</Provider>
)
};
One of the parent components, e.g. App, must wrap the header (or one of its ancestor components) with Context.Provider:
import { Provider } from "./HeaderContext"
...
<Provider>
<Header />
</Provider>

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

Resources