How to get state values from custom component to another parent component - javascript

I have 2 classes (both React.Component). Let's say, that one of these is my own component, which also built on another custom component (in my case, it's React Places Autocomplete).
Just look at this picture
Code here:
//App.js
import React, { Component } from 'react';
import './App.css';
import PlaceAutocomplete from "./places_autocomplete";
class App extends Component {
constructor(props) {
super(props);
this.state = { output: '' };
}
render() {
return (
<div className="App">
<PlaceAutocomplete/>
<p>{this.state.output}</p>
</div>
);
}
}
export default App;
//places_autocomplete.js
import React, { Component } from 'react';
import './PlaceAutocomplete.css';
import PlacesAutocomplete from 'react-places-autocomplete';
class PlaceAutocomplete extends Component {
constructor(props) {
super(props);
this.state = { address: '', output: '' };
}
handleChange = address => {
this.setState({ address });
};
handleSelect = async address => {
this.setState({address: address});
this.state.output = address;
document.getElementById('lsi').blur();
};
searchOptions = {
types: ['(cities)']
};
hidden = (suggest) => {
return suggest == null || suggest === ""
? "autocomplete-dropdown-container-hidden"
: "autocomplete-dropdown-container";
};
render() {
return (
<PlacesAutocomplete
value={this.state.address}
onChange={this.handleChange}
onSelect={this.handleSelect}
searchOptions={this.searchOptions}>
{({ getInputProps, suggestions, getSuggestionItemProps, loading }) => (
<div>
<input value={this.state.address}
id={'lsi'}
{...getInputProps({
placeholder: 'Select a city',
className: 'location-search-input',
})}/>
<div className={this.hidden(suggestions[1])}>
{loading && <div>Loading...</div>}
{suggestions.map(suggestion => {
const className = suggestion.active
? "suggestion-item--active"
: "suggestion-item";
// inline style for demonstration purpose
const style = suggestion.active
? { backgroundColor: '#fafafa', cursor: 'pointer' }
: { backgroundColor: '#ffffff', cursor: 'pointer' };
return (
<div
{...getSuggestionItemProps(suggestion, {
className: className,
style,
})}
>
<span>{suggestion.description}</span>
</div>
);
})}
</div>
</div>
)}
</PlacesAutocomplete>
);
}
}
export default PlaceAutocomplete;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
So, you can see how I tried to find a solution for this. This code mostly looks ugly, because I don't know any other way to implement these feautures.
System info:
The latest React for 17.08.2018 (I don't really remember, but I do know that it's the latest (I installed it just 1 week ago).
This application created by CRA (Create React Application) template. So please, if your solution won't work with this template (I think there's different styles, like ES6 etc. But it's not the point) then add at least an explanation to your answer.

Try lifting the state up to the parent component and use callbacks to share the data. As stated by the react docs, there should be a single "source of truth" for data changes in a React application - this reduces potential bugs and duplicated code. Take a look at . https://reactjs.org/docs/lifting-state-up.html

Related

Why is my React form not updating dynamically?

I am fairly new to software development. I am currently working on a project on Codecademy where the cat should copy what you say, unless it has the tape over it's mouth (pic attached). After many hours, I just can't seem to find why whatever I type into the input field, doesn't dynamically update in the p field. Any guidance would be appreciated!
This is also my first Stack Overflow post, so if I am missing anything, please let me know.
Both states of the cat - without tape, it should copy what is in the input field and vice versa
// CopyCat.js
import React from 'react';
import { styles } from '../styles';
const images = {
copycat: 'https://content.codecademy.com/courses/React/react_photo_copycat.png',
quietcat: 'https://content.codecademy.com/courses/React/react_photo_quietcat.png'
};
export class CopyCat extends React.Component {
render() {
const copying = this.props.copying;
const toggleTape = this.props.toggleTape;
const input = this.props.input;
const handleChange = this.props.handleChange;
return (
<div style={styles.divStyles}>
<h1 style={{ marginBottom: 80 }}>Copy Cat</h1>
<input
type='text'
value={this.input}
onChange={this.handleChange} />
<img
alt='cat'
src={copying ? images.copycat : images.quietcat}
onClick={toggleTape}
style={styles.imgStyles}
/>
<p>{this.copying && this.input}</p>
</div>
);
};
}
// CopyCatContainer.js
import React from 'react';
import ReactDOM from 'react-dom';
import { CopyCat } from '../components/CopyCat';
const images = {
copycat: 'https://content.codecademy.com/courses/React/react_photo_copycat.png',
quietcat: 'https://content.codecademy.com/courses/React/react_photo_quietcat.png'
};
class CopyCatContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
copying: true,
input: ''
};
this.toggleTape = this.toggleTape.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.setState({input: e.target.value})
}
toggleTape() {
this.setState({copying: !this.state.copying})
}
render() {
const copying = this.state.copying;
const toggleTape = this.toggleTape
return (
<CopyCat
copying={this.state.copying}
toggleTape={this.toggleTape} />
);
};
}
ReactDOM.render(<CopyCatContainer />, document.getElementById('app'));
You are not passing the input and the handleChange method as props to the CopyCat component
You need to pass input to <CopyCat /> component like below;
return (
<CopyCat
copying={this.state.copying}
toggleTape={this.toggleTape}
input={this.state.input} />
);
you are not passing handlechange function to copycat component. Pass it and it should work fine. Hope this would help.
You need to pass both input as well as handleChange props to CopyCat component to reflect text change, like this:
render() {
const copying = this.state.copying;
const input = this.state.input;
return (
<CopyCat
copying={copying}
input={input}
toggleTape={this.toggleTape}
handleChange={this.handleChange} />
);
};

Render unique divs for each hovered element

minimum reproducible example: https://codesandbox.io/s/react-hover-example-tu1eu?file=/index.js
I currently have a new element being rendered when either of 2 other elements are hovered over. But i would like to render different things based upon which element is hovered.
In the example below and in the codepen, there are 2 hoverable divs that are rendered; when they are hovered over, it changes the state and another div is rendered. I would like for the HoverMe2 div to render text "hello2". Currently, whether i hover hoverme1 or 2, they both just render the text "hello".
import React, { Component } from "react";
import { render } from "react-dom";
class HoverExample extends Component {
constructor(props) {
super(props);
this.handleMouseHover = this.handleMouseHover.bind(this);
this.state = {
isHovering: false
};
}
handleMouseHover() {
this.setState(this.toggleHoverState);
}
toggleHoverState(state) {
return {
isHovering: !state.isHovering
};
}
render() {
return (
<div>
<div
onMouseEnter={this.handleMouseHover}
onMouseLeave={this.handleMouseHover}
>
Hover Me
</div>
<div
onMouseEnter={this.handleMouseHover}
onMouseLeave={this.handleMouseHover}
>
Hover Me2
</div>
{this.state.isHovering && <div>hello</div>}
</div>
);
}
}
render(<HoverExample />, document.getElementById("root"));
You need to keep the state of item which you have hovered that's for sure
const { Component, useState, useEffect } = React;
class HoverExample extends Component {
constructor(props) {
super(props);
this.handleMouseHover = this.handleMouseHover.bind(this);
this.state = {
isHovering: false,
values: ['hello', 'hello2'],
value: 'hello'
};
}
handleMouseHover({target: {dataset: {id}}}) {
this.setState(state => {
return {
...state,
isHovering: !state.isHovering,
value: state.values[id]
};
});
}
render() {
return (
<div>
<div
data-id="0"
onMouseEnter={this.handleMouseHover}
onMouseLeave={this.handleMouseHover}
>
Hover Me
</div>
<div
data-id="1"
onMouseEnter={this.handleMouseHover}
onMouseLeave={this.handleMouseHover}
>
Hover Me2
</div>
{this.state.isHovering && <div>{this.state.value}</div>}
</div>
);
}
}
ReactDOM.render(
<HoverExample />,
document.getElementById('root')
);
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<div id="root"></div>
You can pass the context text as shown in example. This is working code:
import React, { Component } from "react";
import { render } from "react-dom";
// Drive this using some configuration. You can set based on your requirement.
export const HOVER_Hello1 = "Hello1";
export const HOVER_Hello2 = "Hello2";
class HoverExample extends Component {
constructor(props) {
super(props);
this.handleMouseHover = this.handleMouseHover.bind(this);
this.state = {
isHovering: false,
contextText: ""
};
}
handleMouseHover = (e, currentText) => {
this.setState({
isHovering: !this.state.isHovering,
contextText: currentText
});
}
toggleHoverState(state) {
//
}
render() {
return (
<div>
<div
onMouseEnter={e => this.handleMouseHover(e, HOVER_Hello1)}
onMouseLeave={e => this.handleMouseHover(e, HOVER_Hello1)}
>
Hover Me
</div>
<div
onMouseEnter={e => this.handleMouseHover(e, HOVER_Hello2)}
onMouseLeave={e => this.handleMouseHover(e, HOVER_Hello2)}
>
Hover Me2
</div>
{this.state.isHovering && <div>{this.state.contextText}</div>}
</div>
);
}
}
export default HoverExample;
If the whole point is about linking dynamically messages to JSX-element you're hovering, you may store that binding (e.g. within an object).
Upon rendering, you simply pass some anchor (e.g. id property of corresponding object) within a custom attribute (data-*), so that later on you may retrieve that, look up for the matching object, put linked message into state and render the message.
Following is a quick demo:
const { Component } = React,
{ render } = ReactDOM,
rootNode = document.getElementById('root')
const data = [
{id:0, text: 'Hover me', message: 'Thanks for hovering'},
{id:1, text: 'Hover me too', message: 'Great job'}
]
class HoverableDivs extends Component {
state = {
messageToShow: null
}
enterHandler = ({target:{dataset:{id:recordId}}}) => {
const {message} = this.props.data.find(({id}) => id == recordId)
this.setState({messageToShow: message})
}
leaveHandler = () => this.setState({messageToShow: null})
render(){
return (
<div>
{
this.props.data.map(({text,id}) => (
<div
key={id}
data-id={id}
onMouseEnter={this.enterHandler}
onMouseLeave={this.leaveHandler}
>
{text}
</div>
))
}
{
this.state.messageToShow && <div>{this.state.messageToShow}</div>
}
</div>
)
}
}
render (
<HoverableDivs {...{data}} />,
rootNode
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
As #CevaComic pointed out, you can do this with CSS. But if you want to use React, for example, because your actual problem is more complex, here is the answer.
You will need a way to tell apart the two elements. It could be done with some neat tricks, like setting an unique id to each element, passing a custom argument, or something else.
But I would advise against "cool tricks" as it's more difficult to understand what is going on, and the code is more prone to errors. I think the best way it to use a dumb approach of unique functions for unique elements.
Each onMouseEnter and onMouseLeave has to be an unique function (e.g. handleMouseHover1 and handleMouseHover2), and each of those functions need to control unique state (for example, isHovering1 and isHovering2). Then you have to render the element you want based on the state. Of course, for a real-world code, you will probably want to use more descriptive names to make the code more comprehensible. The full code would look something like this.
class HoverExample extends Component {
state = {
isHovering1: false,
isHovering2: false
};
handleMouseHover1 = () => {
this.setState(({ isHovering1 }) => ({ isHovering1: !isHovering1 }));
};
handleMouseHover2 = () => {
this.setState(({ isHovering2 }) => ({ isHovering2: !isHovering2 }));
};
render() {
const { isHovering1, isHovering2 } = this.state;
return (
<div>
<div
onMouseEnter={this.handleMouseHover1}
onMouseLeave={this.handleMouseHover1}
>
Hover Me1
</div>
<div
onMouseEnter={this.handleMouseHover2}
onMouseLeave={this.handleMouseHover2}
>
Hover Me2
</div>
{isHovering1 && <div>hello1</div>}
{isHovering2 && <div>hello2</div>}
</div>
);
}
}
Also, updated example: https://codesandbox.io/s/react-hover-example-rc3h0
Note: I have also edited the code to add some syntax sugar which exists with newer ECMAScript versions. Instead of binding the function, you can use the arrow function format, e.g. fn = () => { ... }. The arrow function means the this context is automatically bound to the function, so you don't have to do it manually. Also, you don't have to initialize this.state inside the constructor, you can define it as a class instance property. With those two things together, you do not need the constructor at all, and it makes the code a bit cleaner.

react-select can load async data

I'm trying to build a select component using react-select plugin.
In the process of implementing this project, I have some kind of tricky problem with that. Check out my source code here: https://codesandbox.io/s/j148r99695
The problem that I have is I want to fetch all genresList data from the server and mapping them to select component. But somehow or I do wrong something, It's not working. Please see source code above to help me.
I fetch data from Movies component. Its work well and I pass a props to FormFilter component: <FormFilter genresList={this.state.genres} />. And in the FormFilter component, I check this.props.genresList, it's available. But when I'm trying to assign it to FormFilter state and console.log("state", this.state.genres); that. It's empty. Anyone can tell me why?
Default react-select using value and label to display data to select component. But you know some cases we have to custom that. I try it out by using map to transform to other arrays. But It's the best way? How can I custom valueKey and labelKey.
I'm using react-select beta version2.
UPDATE: I was fixed my project. Please check out the link below. Somehow it's not working. I was commend inside source code.
https://codesandbox.io/s/moym59w39p
So to make it works I have changed the FormFilter.js implementation:
import React, { Component } from "react";
import * as Animated from "react-select/lib/animated";
import AsyncSelect from "react-select/lib/Async";
class FormFilter extends Component {
constructor(props) {
super(props);
this.state = {
inputValue: "",
selectedOption: "",
genres: []
};
}
selectGenreHandleChange = newValue => {
const inputValue = newValue.replace(/\W/g, "");
this.setState({ inputValue });
console.log(inputValue);
};
componentDidMount() {
this.genresOption();
}
filterGenres = inputValue => {
const genres = this.genresOption();
//HERE - return the filter
return genres.filter(genre =>
genre.label.toLowerCase().includes(inputValue.toLowerCase())
);
};
promiseOptions = inputValue => {
return new Promise(resolve => { // HERE - you have to return the promise
setTimeout(() => {
resolve(this.filterGenres(inputValue));
}, 1000);
});
};
genresOption() {
const options = [];
const genres = this.props.genresList.genres; //HERE - array is genres in genresList
if (genres && genres instanceof Array) {
genres.map(genre => options.push({ value: genre.id, label: genre.name}));
}
return options;
}
render() {
const { inputValue } = this.state;
if (this.state.genres) console.log("state", this.state.genres);
if (this.props.genresList)
console.log("Movies props", this.props.genresList);
return (
<div className="filter_form">
<span className="search_element full">
<label htmlFor="genres">Genres</label>
<AsyncSelect
className="select genres"
classNamePrefix="tmdb_select"
isMulti
isSearchable="true"
isClearable="true"
cacheOptions
components={Animated}
value={inputValue}
defaultOptions
onInputChange={this.selectGenreHandleChange}
loadOptions={this.promiseOptions}
/>
</span>
</div>
);
}
}
export default FormFilter;
I have write a comment "HERE - something" to let you know what I changed. There are not big problems :)
I did some changed in your FIDDLE and it's works for me
Something like
import React, {Component} from "react";
import { render } from 'react-dom';
import Movies from './Movies';
import "./styles.css";
class App extends Component {
render() {
return (
<div className="App">
<Movies />
</div>
);
}
}
let a = document.getElementById("root");
render(<App />, a);

Display some text in react depending on the switch case

I have a dropdown populated from a Web Service, what I want is to display some text according to the selection made. For example the first option in the Dropdown is Buy n and Save m so in a p tag I want to display Buy 2 and Save $1.5 I know this is work for a switch and the position of the array is going to be my "CASE" in order to know what to display or not but I'm new to react and also in programming so I need help..
import React from 'react';
import DropDownMenu from 'material-ui/DropDownMenu';
import MenuItem from 'material-ui/MenuItem';
import cr from '../styles/general.css';
export default class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
OfferTypeData: [],
OfferTypeState: '',
};
this.handleChange = this.handleChange.bind(this);
this.renderOfferTypeOptions = this.renderOfferTypeOptions.bind(this);
}
componentDidMount() {
const offerTypeWS = 'http://localhost:8080/services/OfferType/getAll';
fetch(offerTypeWS)
.then(Response => Response.json())
.then(findResponse => {
console.log(findResponse);
this.setState({
OfferTypeData: findResponse
});
});
}
handleChange(event, index, value) {this.setState({value});}
handleChangeDiscountType(event, index, value) {
this.setState({ OfferTypeState: (value) });
}
renderOfferTypeOptions() {
return this.state.OfferTypeData.map((dt, i) => {
return (
<MenuItem
key={i}
value={dt.offerTypeDesc}
primaryText={dt.offerTypeDesc} />
);
});
}
render() {
return (
<div className={cr.container}>
<div className={cr.rows}>
<div>
<DropDownMenu
value={this.state.OfferTypeState}
onChange={this.handleChangeDiscountType}>
<MenuItem value={''} primaryText={'Select Offer Type'} />
{this.renderOfferTypeOptions()}
</DropDownMenu>
<br/>
<p>{DISPLAY SOME TEXT HERE}</p>
</div>
</div>
</div>
);
}
}
Thanks in advance!
Regards.
Create a component which passes a callback to the dropdown, this callback will update the state of the container which will in turn set the props of the display. This is very common in React and is the basis of how the compositional pattern works. If you need to share data between two components just put them in a container and lift the state to the parent component. These components are usually called containers and there is a bunch of documentation on it.
This is a good starting point: https://reactjs.org/docs/lifting-state-up.html
A rough layout would be something like this.
class Container extends React.Component {
constructor(props) {
super(props);
// Don't forget to bind the handler to the correct context
this.changeText = this.changeText.bind(this);
}
changeText(text) {
this.setState({text: text});
}
render() {
return (
<DropDown callback={this.changeText} />
<Display text={this.state.text} />
)
}
}
Display component...
const Display = (props) => (
<p>{this.props.text}</p>
)

switch components in react js

I'm doing a singlepage application and would like to switch a component.
Here is an image how it looks like:
If I click on the button in component 3, I will switch the component 3 with 5.
So maybe like component 3 is a view of all projects and if I click on one project, I will see a detail view of the project with some information.
I created two different components for this.
All other components should stay at the same place.
Here is my code how I switch the components:
this.state.detailVisible
? <ProjectDetailView/>
: null
I'm not sure if is the correct react way to do it. Also I have two different css files for component 3 and 5. If I'm switching the two component, I have some class name irritations with my css.
If it's a better way to do it with routers?
How is the react way to do it?
thanks for your help :)
It all depends on your needs, if you need to render both component3 and component5 then a route won't be much of a help.
If you need to render only one of them then a route can be handy.
as for the syntax i prefer this:
this.state.detailVisible && <ProjectDetailView/>
here is a simple example:
const components = ["Component1", "Component2", "Component3"];
class Component extends React.Component {
constructor(props) {
super(props);
this.state = {
showDetails: false
};
this.onComponentClicked = this.onComponentClicked.bind(this);
}
onComponentClicked(e) {
this.setState({
showDetails: !this.state.showDetails
});
}
render() {
const { name } = this.props;
const { showDetails } = this.state;
return (
<div>
<div>Component{name}</div>
<button onClick={this.onComponentClicked}>Toggle Details</button>
{showDetails && <ComponentDetails name={name} />}
</div>
);
}
}
const ComponentDetails = ({ name }) => (
<div>
<div>Details of component{name}</div>
</div>
);
class App extends React.Component {
render() {
return (
<div>
<h2>Parent</h2>
{components.map(c => {
return (
<div>
<Component name={c} />
<hr />
</div>
);
})}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Categories

Resources