I have implemented React Context API and I am trying to update the state defined inside the Provider via an onClick function inside a child component.
This is what I have done so far, in the App.js I have:
import { createContext } from 'react';
const MyContext = React.createContext();
export class MyProvider extends Component {
state = {
currPrj: ''
}
handleBtnClick = prjCode => {
this.setState({
currPrj: prjCode
})
}
render() {
return(
<MyContext.Provider value={{
state: this.state
}}>
{this.props.children}
</MyContext.Provider>
)
}
}
export const MyComsumer = MyContext.Consumer;
Inside my child component I have:
import React, { Component } from "react";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
import { MyComsumer } from "../../index";
export class ProjectCard extends Component {
constructor(props) {
super(props);
this.state = {
// currPrj: ''
};
}
render() {
return (
<MyComsumer>
{(context) => (
<div className="card card-project">
<p>{context.state.currPrj}</p>
<div className="content">
<div className="author">
<Link to={ `projects/${this.props.code}/detail/info` } onClick={() => handleBtnClick(this.props.code) }>
<h4 className="title">
{this.props.title}
</h4>
</Link>
</div>
</div>
)}
</MyComsumer>
);
}
}
export default ProjectCard;
This way I get the following error
Failed to compile
./src/components/ProjectCard/ProjectCard.jsx
Line 32: 'handleBtnClick' is not defined no-undef
Search for the keywords to learn more about each error.
I don't get it why, because:
<p>{context.state.currPrj}</p>
throws no error...
Plus, is this.props.code passed correctly to the function?
Many thanks.
you can follow this:
My Fiddle: https://jsfiddle.net/leolima/ds0o91xa/1/
class Parent extends React.Component {
sayHey(son) {
alert('Hi father, this is son '+son+' speaking');
}
render() {
const children = React.Children.map(this.props.children, (child, index) => {
return React.cloneElement(child, {
someFunction: () => this.sayHey(index)
});
});
return (<div>
<b>Parent</b>
<hr />
{children}
</div>);
}
}
class Child extends React.Component {
render() {
return <div>
<button onClick={() => this.props.someFunction()}>Child</button>
</div>;
}
}
ReactDOM.render(
<Parent>
<Child />
<Child />
</Parent>,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="container">
</div>
There is linter error because handleBtnClick is not defined. It's a method of another class, not standalone function.
It's not available in the scope of context consumer function. If consumers are supposed to update the context, updater function should be a part of the context:
<MyContext.Provider value={{
state: this.state,
update: this.handleBtnClick
}}>
And used like:
context.update(this.props.code)
Related
I am trying to fetch data from GitHub user API and store the JSON in an array, and then and then loop through the array and create Card components dynamically on submission of form, but the Card components doesn't render even though the array is updated with the newly added user.
I have added relevant code files and image of React component tree on dev console before and after valid form submission. Thanks.
App.js
import React from "react";
import Form from "./Form";
import Cardlist from "./Cardlist";
import ProfileData from "../JSON";
export default class App extends React.Component {
testData = ProfileData();
state = {
profiles: this.testData,
};
addNewProfile = (profile) => {
this.setState({
profiles: [...this.state.profiles, profile],
});
};
render() {
return (
<div>
<h4>GITHUB PROFILE CARDS</h4>
<Form onSubmit={this.addNewProfile} />
<Cardlist profiles={this.state.profiles} />
</div>
);
}
}
Form.js
import React from "react";
export default class Form extends React.Component {
state = { userName: "" };
handleSubmit = (event) => (
event.preventDefault(),
fetch(`https://api.github.com/users/${this.state.userName}`)
.then((response) => {
return response.json();
})
.then((data) => {
this.props.onSubmit(data);
})
);
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<input
type="text"
placeholder="username"
value={this.state.userName}
onChange={(event) =>
this.setState({ userName: event.target.value })
}
/>
<button>Add Card</button>
</form>
</div>
);
}
}
Cardlist.js
import React from "react";
import Card from "./Card";
export default class Cardlist extends React.Component {
profile = this.props.profiles.map((element) => (
<Card key={element.id} {...element} />
));
render() {
return (<div>{this.profile}</div>);
}
}
Card.js
import React from "react";
export default class Card extends React.Component {
render() {
return(
<div style = {{display: 'flex'}}>
<img src = {this.props.avatar_url} style = {{width: '100px', height: '100px'}}/>
<div>
<h6>{this.props.name}</h6>
<h6>{this.props.name}</h6>
</div>
</div>
);
}
}
component tree before
array before
component tree after
array after
You can change your CardList component to:
import React from "react";
import Card from "./Card";
export default class Cardlist extends React.Component {
render() {
return (
this.props.profiles.map((element) => (
<Card key={element.id} {...element} />
))
);
}
}
The problem with your CardList component is that you have a class property (profile) which is initialized when the CardList component is created but the class property does not update its value when the props is updated.
PS: you can also convert your class property into a function
profile = () => (
this.props.profiles.map((element) => (
<Card key={element.id} {...element} />
))
);
and the on render
render() {
return (<div>{this.profile()}</div>);
}
In Cardlist.js, instead of using a class variable, directly return the mapped elements.
import React from "react";
import Card from "./Card";
export default class Cardlist extends React.Component {
render() {
return this.props.profiles.map((element) => (
<Card key={element.id} {...element} />
));
}
}
Working demo on Codesandbox.
Although your CardList is received a new props from parent and then get rerender, this.profile outside your render function, so it never get updated. Just put it inside your render function and it works fine
class Cardlist extends React.Component {
render() {
const profile = this.props.profiles.map((element) => (
<Card key={element.id} {...element} />
));
return <div>{profile}</div>;
}
}
codesanbox
How can I share the data value that I have from the handleClick function inside the Module class to the InputSelect class, all the classes are in the same file?? I'm not using Redux.
I can't use props because there is not a relationship between these classes..
Should I nest all the classes??
Any suggestion??
import React, { useState } from "react";
const array = [ ];
class InputSelect extends React.Component {
render() {
return (
<div>
{ 'Put it here........' }
</div>
)
}
}
class Module extends React.Component {
handleClick(e) {
console.log(e.currentTarget.textContent);
}
render() {
return (
<div
onClick={this.handleClick}
>
{this.props.id}
</div>
);
}
}
function App() {
return (
<div>
<Menu availableModules={array} />
</div>
);
}
export default App;
Correctly stated by #Federkun above, you should use React context.
Check the react docs here
Good afternoon friends,
My pages and components are arranged in the main class of my application, can I pass some results from any component or page to the main class and get this property from main class to any other component.
To describe question well I will show an example:
This is my main class App.js
import React, {Component} from 'react';
import { BrowserRouter as Router, Route} from "react-router-dom";
import HomePage from "./Pages/HomePage";
import NavBar from "./Components/NavBar";
import PaymentStatus from "./Pages/PaymentStatus";
class App extends Component {
constructor(props) {
super(props);
this._isMounted = true;
this.state = {};
};
render() {
return (
<Router>
<NavBar/>
<Route name={'Home'} exact path={'/'} component={HomePage}/>
<Route name={'PaymentStatus'} exact path={'/payment-status/:tId'} component={PaymentStatus}/>
</Router>
);
}
}
export default App;
Now my navigation bar component: NavBar.js
import React, {Component} from 'react';
class NavBar extends Component {
constructor(props) {
super(props);
this._isMounted = true;
this.state = {};
};
_makeSomething =async() => {
// Somw function that returns something
}
render() {
return (
<div>
<div id={"myNavbar"}>
<div>
<a onClick={()=>{this._makeSomething()}} href={'/'}/> Home</a>
<a onClick={()=>{this._makeSomething()}} href={"/payment-status"} />Payment Status</a>
</div>
</div>
);
}
}
export default NavBar;
HomePage.js
import React, {Component} from 'react';
class HomePage extends Component {
constructor(props) {
super(props);
this._isMounted = true;
this.state = {};
};
async componentDidMount() {
console.log(this.props.match.params.tId)
};
render() {
return (
<div>
<div id={"main"}>
<div>
<p>This is home page</p>
</div>
</div>
);
}
}
export default HomePage;
PaymentStatusPage.js
import React, {Component} from 'react';
class PaymentStatusPage extends Component {
constructor(props) {
super(props);
this._isMounted = true;
this.state = {};
};
async componentDidMount() {
console.log(this.props.match.params.tId)
};
render() {
return (
<div>
<div id={"status"}>
<div>
<p>This is payment Status Page</p>
</div>
</div>
);
}
}
export default PaymentStatusPage;
Now here is the question:
Can I pass to App.js events (or props) when HomePage.js or PaymentStatusPage.js or when something was changed in NavBar.js
Also, want pass received peprops to any component.
Thank you.
You can decalare method in class App and then pass it to another component via props.
For example
Then you can call this method in MyComponent and pass some value to it. This is the way you pass value from subcomponent to parent component. In method in App you can simply use setState.
What's left to do is to pass this new state attribute to another component via props.
To pass value to component, while using you have to change
<Route component={SomeComponent}
To
<Route render={() => <SomeComponent somethingChanged={this.somethingChangedMethodInAppClass}}/>
Hope it helps!
EDIT: You can also use Redux to externalize state and reuse it in child components
You have two options here:
Keep all of your state in your parent component, App, and pass any props down to your children component, even actions that could update the parent state. If another children uses that state, then that child will be rerendered too.
Manage your state with Redux and make it available for all your components.
I created a small example out of your scenario.
In this example, the App component has a state with a property called title and a function that is passed down via props to the Navbar.
class App extends Component {
constructor(props) {
super(props);
this._isMounted = true;
this.state = {
title: "Home Page"
};
}
_makeSomething = title => {
this.setState({ title: title });
};
render() {
return (
<Router>
<NavBar clicked={this._makeSomething} />
<Route
name={"Home"}
exact
path={"/"}
component={() => <HomePage title={this.state.title} />}
/>
<Route
name={"PaymentStatus"}
exact
path={"/payment-status/:tId"}
component={() => <PaymentStatus title={this.state.title} />}
/>
</Router>
);
}
}
The components HomePage and PaymentStatus will get that title as props from the App's state and NavBar will get the _makeSomething function as props. So far, all that function does is update the state's title.
class NavBar extends Component {
constructor(props) {
super(props);
this._isMounted = true;
this.state = {};
}
render() {
return (
<div>
<div id={"myNavbar"}>
<NavLink
onClick={() => {
this.props.clicked("Home Page");
}}
to={"/"}
>
{" "}
Home
</NavLink>
<NavLink
onClick={() => {
this.props.clicked("Payment Page");
}}
to={"/payment-status/1"}
>
Payment Status
</NavLink>
</div>
</div>
);
}
}
In the Navbar, when the function I passed down from App as props is clicked, it will go all the way back to the App component again and run _makeSomething, which will change the App's title.
In the mantime, the components HomePage and PaymentStatus received title as props, so when the state's title is changed, these two children component will change too, since their render function relies on this.props.title.
For example, HomePage:
class HomePage extends Component {
constructor(props) {
super(props);
this._isMounted = true;
this.state = {};
}
async componentDidMount() {
console.log(this.props.match.params.tId);
}
render() {
return (
<div>
<div id={"main"}>
<p>This is {this.props.title}</p>
</div>
</div>
);
}
}
Like I said before, by keeping your state in the parent component and sending down to the children component just what they need, you should be able to accomplish what you need.
A note: I did change the anchor tag from <a> to NavLink which is what you're supposed to use with react-router-dom if you don't want a complete refresh of the page.
The full code can be found here:
Have a look at Context. With this you can pass an object from a Provider to a Consumerand even override properties with nested providers: https://reactjs.org/docs/context.html
AppContext.js
export const AppContext = React.createContext({})
App.js
someFunction = ()=>{
//implement it
}
render() {
const appContext = {
someFunction: this.someFunction
}
return (
<AppContext.Provider value={appContext}>
<Router>
<NavBar/>
<Route name={'Home'} exact path={'/'} component={HomePage}/>
<Route name={'PaymentStatus'} exact path={'/payment-status/:tId'} component={PaymentStatus}/>
</Router>
</AppContext>
);
}
Homepage.js
class HomePage extends Component {
constructor(props) {
super(props);
this._isMounted = true;
this.state = {};
};
async componentDidMount() {
console.log(this.props.match.params.tId)
this.props.appContext.someFunction(); //calls the function of the App-component
};
render() {
return (
<div>
<div id={"main"}>
<div>
<p>This is home page</p>
</div>
</div>
);
}
}
export default (props) => (
<AppContext.Consumer>
{(appContext)=>(
<HomePage {...props} appContext={appContext}/>
)}
</AppContext.Consumer>
)
You can also use this mechanic with function components. I'm normally encapsulating the Consumer to an extra component. So all values available for the component as normal property and not just inside the rendered components.
This is my code:
import React, { Component } from 'react'
import { BackButton } from 'components/button'
class LandingHeader extends Component {
render() {
const back = (props) => <BackButton forcedBackUrl={props.back.forcedBackUrl} />
return (
<div>
{back}
{this.props.children}
</div>
)
}
}
export default LandingHeader
If i put the <BackButton> component directly it works but if I use a stateless component and return it inside this one it wont. What im missing?
Im following the official documentation (https://facebook.github.io/react/docs/reusable-components.html) and I can't see whats wrong. Thanks.
Looking at the facebook documentation that you provided they give the example of :
const HelloMessage = (props) => <div>Hello {props.name}</div>;
ReactDOM.render(<HelloMessage name="Sebastian" />, mountNode);
and not just returning {HelloMessage}
therefore replace
{back}
with
<Back />
and you should be good to go
You've declared a ReactClass but you aren't rendering it - you have to turn it into a ReactElement:
const Back = (props) => <BackButton forcedBackUrl={props.forcedBackUrl} />
return (
<div>
<Back {...this.props.back} />
{this.props.children}
</div>
);
You need to inject the props into the component
import React, { Component } from 'react'
import { BackButton } from 'components/button'
class LandingHeader extends Component {
render() {
const back = (props) => <BackButton forcedBackUrl={props.back.forcedBackUrl} />
return (
<div>
{back(this.props)}
{this.props.children}
</div>
)
}
}
My 'store.js' does the following:
export default function configureStore(initialState = {todos: [], floatBar: {} }) {
return finalCreateStore(rootReducer, initialState)
}
Then in my 'client.js', I have the intialized states, but didn't define 'todos' array, and set up the Router:
let initialState = {
floatBar: {
barStatus: false,
id: 0
}
}
let store = configureStore(initialState)
render(
<div>
<Provider store={store}>
<Router history={hashHistory}>
<Route
path="/"
component={App}
>
<Route
component={FirstPage}
path="firstpage"
/>
<Route
component={NewPage}
path="/newpage/:id"
/>
</Route>
</Router>
</Provider>
</div>,
document.getElementById('app')
)
Then in my 'item.js' component, which is a child of 'FirstPage.js', it gets an object 'todo' and retrieves the '.id', which is an object from the 'todos' object-array (inside the render() return{}), I have the following:
<Link to={`/newpage/${this.props.todo.id}`}>Link1</Link>
Lastly, in my newly linked page, 'NewPage.js', I want to be able to use the same exact 'todo' object in 'item.js', so I can call 'todo.id' and such. How can I do so?
Could anyone show the proper way to do this using redux react-router? Would really appreciate it.
**UPDATE
**NEWEST UPDATE for actions
actions.js has all of my action creators inside:
import * as actions from '../redux/actions'
class NewPage extends Component{
handleCommentChange(){
this.props.actions.updateComment()
}
render(){
return()
}
}
function mapDispatchToProps(dispatch){
return{
actions: bindActionCreators(actions, dispatch)
}
}
export default connect(
mapDispatchToProps
)(NewPage);
You can access to "todo id" from props.params.id . Also you can access to props.params of NewPage through "ownProps" in "mapStateToProps"
import {connect} from "react-redux";
import React, { Component } from 'react'
import { Divider } from 'material-ui'
const styles = {
title:{
color: 'white',
textAlign: 'left',
marginLeft: 30
}
}
class NewPage extends Component{
render(){
return(
<div>
<div style={styles.title}>
<font size="4">
{this.props.todo.title}
</font>
</div>
<Divider style={{backgroundColor:'#282828'}}/>
<p style={{color: 'white'}}>{this.props.todo.detail}</p>
</div>
)
}
}
const mapStateToProps=(state, ownProps)=>{
let todo = state.todos.filter(todo=>todo.id==ownProps.params.id);
return{
todo:todo[0]
}};
export default connect(mapStateToProps)(NewPage);
Another approach, especially useful for dynamic props, is to clone the child component that gets injected as
props by React Router, which gives you the opportunity to pass additional props in the process.
example
class Repos extends Component {
constructor(){...}
componentDidMount(){...}
render() {
let repos = this.state.repositories.map((repo) => (
<li key={repo.id}>
<Link to={"/repos/details/"+repo.name}>{repo.name}</Link>
</li>
));
let child = this.props.children && React.cloneElement(this.props.children,
{ repositories: this.state.repositories }
);
return (
<div>
<h1>Github Repos</h1>
<ul>
{repos}
</ul>
{child}
</div>
);
}
}
////////////////////////////
class RepoDetails extends Component {
renderRepository() {
let repository = this.props.repositories.find((repo)=>repo.name === this.props.params.
repo_name);
let stars = [];
for (var i = 0; i < repository.stargazers_count; i++) {
stars.push('');
}
return(
<div>
<h2>{repository.name}</h2>
<p>{repository.description}</p>
<span>{stars}</span>
</div>
);
}
render() {
if(this.props.repositories.length > 0 ){
return this.renderRepository();
} else {
return <h4>Loading...</h4>;
}
}
}