react context rerenders every child of Provider? - javascript

import React, { useReducer, useEffect ,Component} from "react";
import ReactDOM from "react-dom";
const AppContext = React.createContext();
import React from "react";
const withRandomColors = WrappedComponent => {
return class RandomColors extends React.Component {
constructor() {
super();
this.randomColors = [
"red",
"blue",
"green",
"cyan",
"lavender",
"skyblue",
"orange",
"pink",
"yellow"
];
}
getRandomColors() {
const num = Math.floor(Math.random() * 10) % 9;
return this.randomColors[num];
}
render() {
console.log("Rerendering wrapper Component");
return <WrappedComponent randomColor={this.getRandomColors()} />;
}
};
};
class Number extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {}
render() {
console.log("rendering Number Component");
return (
<AppContext.Consumer>
{({ number }) => {
return (
<div style={{ backgroundColor: `${this.props.randomColor}` }}>
{number} <br />
</div>
);
}}
</AppContext.Consumer>
);
}
}
class Text extends React.Component {
constructor(props) {
super(props);
}
render() {
console.log("rendering Text Component");
return (
<AppContext.Consumer>
{({ text }) => (
<div style={{ backgroundColor: `${this.props.randomColor}` }}>
{text} <br />
</div>
)}
</AppContext.Consumer>
);
}
}
const WrappedText=withRandomColors(Text);
const WrappedNumber=withRandomColors(Number);
class App extends Component {
constructor() {
super();
this.state = {
number: Math.random() * 100,
text: "testing context api"
};
}
updateNumber = () => {
const randomNumber = Math.random() * 100;
this.setState({ number: randomNumber });
};
render() {
console.log("rendering app")
return (
<AppContext.Provider value={this.state}>
<div>
<h1>Welcome to React</h1>
<WrappedNumber />
<WrappedText />
<button onClick={this.updateNumber}>Change Number </button>
</div>
</AppContext.Provider>
);
}
}
ReactDOM.render(
<App />,
mountNode
);
when clicked on ChangeNumber button console displays
rendering app
Rerendering wrapper Component
Rerendering Number Component
Rerendering wrapper Component
Rerendering Text Component
and background color changes for both number and text.
React context is supposed to rerender only consumers for the provider right? why is it rerendering All the children of Provider?
I expected that only number changes keeping background color of Number and Text as same and below output from console once clicked on Change Number button since only consumers are supposed to rerender not the Text and Number components.
rendering app
what am I missing?
I took the code from sandbox

WrappedNumber and WrappedText re-render when you update a state in the App component because in the Virtual DOM they come in the same hierarchy as the Provider and when the parent component updates the child components are updated too.
To avoid them from re-rendering you could provide them as children of App like
class App extends Component {
constructor() {
super();
this.state = {
number: Math.random() * 100,
text: "testing context api"
};
}
updateNumber = () => {
const randomNumber = Math.random() * 100;
this.setState({ number: randomNumber });
};
render() {
console.log("rendering app")
return (
<AppContext.Provider value={this.state}>
<div>
<h1>Welcome to React</h1>
{this.props.children}
<button onClick={this.updateNumber}>Change Number </button>
</div>
</AppContext.Provider>
);
}
}
ReactDOM.render(
<App >
<WrappedNumber />
<WrappedText />
</App>,
mountNode
);

Related

Values of All of the Buttons Changing When Only Clicking On One of the Buttons

I'm currently working on the counter buttons, but every time I click on one of the buttons, the values of all buttons automatically change. I'm new to reactjs so it would be great if someone could point me to the right direction. Thanks!
This is what my code looks like:
mport React from "react";
import {Card} from 'react-bootstrap';
import Button from '#material-ui/core/Button';
import "./Box.css"
//const soup = require("./soup.jpeg");
import AppBar from '#material-ui/core/AppBar';
import Typography from '#material-ui/core/Typography';
import { Link } from "react-router-dom";
class MenuPage extends React.Component {
constructor(props){
super(props);
this.state = {
foodName : '',
price : 0,
amount : 0,
counter: 0
}
}
increment(){
this.setState({
counter: this.state.counter + 1
});
}
decrement(){
if(this.state.counter <= 0){
this.setState({
counter:0
});
}else {
this.setState({
counter: this.state.counter - 1
});
}
}
render() {
const cardInfo = [
{image : "" , title: "Soup", price: "$6.0"},
{image : "", title: "Pancakes", price:"$7.4"},
];
const renderCard = (card,index) => {
return(
<Card style={{ width: '18rem' }} key = {index} className="box">
<Card.Img variant="top" src = {card.image} />
<Card.Body>
<Card.Title>{card.title}</Card.Title>
<Card.Text>
{card.price}
</Card.Text>
<button type="button" className="btn btn-danger" id="minus" onClick={this.decrement.bind(this)}>-</button>
<input type="text" id="index" value={this.state.counter}/>
<button type="button" className="btn btn-success" id="plus" onClick={this.increment.bind(this)}>+</button>
</Card.Body>
</Card>
)
}
return <div className= "grid">{cardInfo.map(renderCard)}</div>;
}
}
export default MenuPage;
This is what my UI currently looks like:
The MenuPage keeps the counter state, and each separate card just shows the state of the menupage.
If you want each card to have its own state, you have to make the card a component. Then you give the Card Component a state with a counter.
The menupage can then show a ton of Card components. Then they will each have their own state.
Example Card Component with counter state
class Card extends React.Component {
constructor(props) {
super(props);
this.state = { clicks: 0 };
}
buttonClicked() {
this.setState({clicks: this.state.clicks+1});
}
render() {
return (
<div>
<h4>{this.props.name}</h4>
<div>Clicks: {this.state.clicks}</div>
<button onClick={()=>this.buttonClicked()}>Add one</button>
</div>
);
}
}
Rendering multiple cards, each with their own counter state
class MenuPage extends React.Component {
constructor() {
super();
}
render() {
return (
<div>
<Card name="Noodles"/>
<Card name="Pancakes"/>
<Card name="Soup"/>
</div>
);
}
}

Something went wrong!Element type is invalid: expected a string (for built-in components) or a class/function For HOC

import React, { useState } from 'react';
import CreateProject from './CreateProjectWithName';
function Enhanced(WrappedComponent) {
// ...and returns another component...
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
render() {
return <WrappedComponent data={this.state.data} {...this.props} />;
}
};
}
const Wrapper = (props) => {
const Comp = Enhanced(<CreateProject />);
return (
<div>
<Comp />
<div>
{/* <button onClick={() => updateView()} /> */}
</div>
</div>
);
};
This code block just won't work. What am I doing wrong? Which principal fundamental of React am I missing out on? What is the correct way of rendering an HOC?
The HOC takes in a component reference what you are providing it is an instance when you use it like
const Comp = Enhanced(<CreateProject />);
The correct way would be
const Comp = Enhanced(CreateProject);
Also for performance reasons and for proper execution of lifecycle you must create a wrapped component with HOC outside of your component
const Comp = Enhanced(CreateProject);
const Wrapper = (props) => {
return (
<div>
<Comp />
<div>
{/* <button onClick={() => updateView()} /> */}
</div>
</div>
);
};
function Enhanced(WrappedComponent) {
// ...and returns another component...
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
render() {
return <WrappedComponent data={this.state.data} {...this.props} />;
}
};
}
const Wrapper = (props) => {
const Comp = Enhanced(CreateProject );
return (
<div>
<Comp />
<div>
{/* <button onClick={() => updateView()} /> */}
</div>
</div>
);
};

How to pass props from child component to parent component to another child component in ReactJS?

I'm currently creating a search bar where App is the parent and Header, Home are the children. I'm trying to pass the input from Header to Home via App but it seems that when I try to load in {this.state.searchValue} it does nothing.
I'm lost of where I am wrong in my code. I'm also using Route to route the props from Header to Home.
Here is my code:
Header.js (Child 1)
class Header extends Component {
constructor(props) {
super(props);
this.state = {
search: "",
};
}
onChange = (event) => {
this.setState({ search: event.target.value });
};
submitSearch = (event) => {
event.preventDefault();
console.log(this.state.search);
this.props.passSearchData(this.state.search);
};
render() {
return (
<React.Fragment>
<nav className="navbar navbar-expand-lg navbar-light bg-light">
<form className="form-inline">
<input
className="form-control mr-sm-2"
type="text"
placeholder="Search"
onChange={this.onChange}
/>
<button
className="btn btn-danger"
type="submit"
onClick={this.submitSearch}
>
Search
</button>
</form>
</nav>
</React.Fragment>
);
}
}
export default Header;
App.js (Parent)
class App extends Component {
constructor() {
super();
this.state = {
searchValue: "",
};
}
handleSearchData = (search) => {
this.setState({ searchValue: search });
};
componentDidMount() {
this.props.getItems();
}
render() {
return (
<div className="App">
<Router>
<Header passSearchData={this.handleSearchData} />
<Route
exact
path="/"
render={(props) => (
<Home {...props} searchValue={this.state.searchValue} />
)}
/>
</Router>
<Footer />
</div>
);
}
}
Home.js
class Catalog extends Component {
constructor(props) {
super(props);
this.state = {
};
}
render() {
return (
<div>
<p>{this.props.searchValue}</p>
</div>
);
}
}
I think using react context better for this problem, because passing state between routes quite painful
First u declare your own Provider to act as intermediary between components.
The context will save all the application state. and to consume at your components, simply use useContext and pass the Context u want to use, at this useCase, i call it AppContext. by using the same context, your components get the same state and trigger update immediately
The solution i provide is using functional component. If u are using class Component, just simply create a functional component, then pass the context to the class component
import React, { useContext, useState } from 'react'
const AppContext = React.createContext({})
const AppProvider = props => {
const [currentState, setCurrentState] = useState(null)
const handleState = value => {
setCurrentState(value)
}
const contextValue = { handleState, currentState }
return (
<AppContext.Provider value={contextValue}>
{props.children}
</AppContext.Provider>
)
}
const Child1 = props => {
const { handleState } = useContext(AppContext)
const handleClick = e => {
handleState(e.target.values)
}
return (
<button onClick={handleClick}>Change State</button>
)
}
const Child2 = props => {
const { currentState } = useContext(AppContext)
return (
<h1>{currentState}</h1>
)
}
const Parent = props => {
return (
<Router>
<AppProvider>
<Route component={Child1} />
<Route component={Child2} />
</AppProvider>
</Router>
)
}
<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>

How to transfer data from react child component to parent component

I need to figure out how to transfer the data i receive in a child component to the one in parent component. I need to set the console log i receive in the child to transfer to the parent component state.
Currently I have:
Child Comp:
import Picker from 'react-giphy-component'
import React, { Component, PropTypes } from 'react'
class AddGif extends Component {
log (gif) {
console.log(gif.original.mp4)
}
returnedGifObject = gif ;
render () {
return (
<div>
{/* <button></button> */}
<Picker onSelected={this.log.bind(this)} />
</div>
)
}
}
export default AddGif;
parent element
class PostBox extends Component {
constructor(props){
super(props)
this.state = {
title: null,
postBody: null,
giphyUrl: null,
displayGifPicker: false
}
}
getGifState = (selectedUrl) => {
this.setState({ giphyUrl: selectedUrl})
}
render () {
const {title, postBody} = this.state
const displayGifPicker = this.state.displayGifPicker
return (
<Grid item xl={8}>
{/* <Card className={classes.card} style={mt4}> */}
<Card style={mt4}>
<CardContent >
<PostInput onChange={this.handleInputChange} onSubmit={this.handleSubmit}/>
{displayGifPicker ? (<AddGif selectedGif = {this.getGifState} />) : (<Button size="small" onClick={this.displayGifPicker} ><button>Add Gif</button></Button>)}
<CardActions>
{/* <Button size="small">Submit VH5</Button> */}
</CardActions>
</CardContent>
</Card>
</Grid>
)
}
}
You passed the function prop to children component. Then In Children component just call it :
log = (gif) => {
const { selectedGif } = this.props
console.log(gif.original.mp4)
selectedGif(gif.original.mp4)
}

getting the value of child component

I'm trying to get the value of child component yet not successful. Here what I am working on ...
import React from "react";
import Tooltip from "rc-tooltip";
import Slider, { Range } from "rc-slider";
const Handle = Slider.Handle;
const handle = props => {
const { value, dragging, index, ...restProps } = props;
return (
<Tooltip
prefixCls="rc-slider-tooltip"
overlay={value}
visible={dragging}
placement="top"
key={index}
>
<Handle value={value} {...restProps} />
</Tooltip>
);
};
const Slider = props => {
return (
<div>
<div style={{ width: 300, margin: 30 }}>
<p>{this.props.title}</p>
<Slider min={0} max={10} defaultValue={5} handle={handle}/>
</div>
</div>
);
};
export default Slider;
Main App.js
import Slider from '.....'
class App extends Component{
constructor(props){
super(props);
this.state = {
val: 0
}
}
render() {
return(
<Slider onChange={this.state.value} />
)
}
}
I am looking to get the value to be updated to this App.js state as the slider is being dragged. onChange is not updating the state. How should I modify so that slider value gets updated on this.state.value.
The Slider component from rc-slider has an onChange prop event which is a function. You need to pass this method to slider and update the state instead of just passing the state value
import React from "react";
import Tooltip from "rc-tooltip";
import Slider, { Range } from "rc-slider";
const Handle = Slider.Handle;
const handle = props => {
const { value, dragging, index, ...restProps } = props;
return (
<Tooltip
prefixCls="rc-slider-tooltip"
overlay={value}
visible={dragging}
placement="top"
key={index}
>
<Handle value={value} {...restProps} />
</Tooltip>
);
};
const Slider = props => {
return (
<div>
<div style={{ width: 300, margin: 30 }}>
<p>{this.props.title}</p>
<Slider min={0} max={10} defaultValue={5} onChange={props.onChange} handle={handle}/>
</div>
</div>
);
};
export default Slider;
class App extends Component{
constructor(props){
super(props);
this.state = {
val: 0
}
}
onChange=(value) => {
this.setState({val: value});
}
render() {
return(
<Slider onChange={this.onChange} />
)
}
}
Here is the Live code
https://codesandbox.io/s/n9y97y55kp
Let me know if you have any doubts
You just have to pass the event from child to parent component to update the values.
according to rc-slider document, you should pass a function to Slider onChange.
import Slider from '.....'
class App extends Component {
constructor(props) {
super(props);
this.state = {
val: 0
}
}
handleSliderChange = (value) => {
console.log(value) // maybe a string or a object
this.setState({val: value})
}
render() {
return (
<Slider onChange={this.handleSliderChange} />
)
}
}
above code should work

Categories

Resources