ComponentDidMount does not work - props do not pass to child component - javascript

I have to pass data from MyWeather to MyWeatherData and it occurs that props does not pass into child component, when I use componentDidMount in MyWeatherData. Now I'm using componentDidUpdate in MyWeatherData and it works, but it produces an error: Can only update a mounted or mounting component. Please check the code for the ... component. Does anybody knows how to correct this?
When I pass: <MyWeatherData lat="53.0038" lon="20.0458"/> it works with ComponentDidMount.
import React, {Component} from "react";
import MyWeatherData from "./MyWeatherData";
export default class MyWeather extends Component {
constructor() {
super();
this.state = {
latitude: "",
longitude: ""
}
this.getMyLocation = this
.getMyLocation
.bind(this)
}
componentDidMount() {
this.getMyLocation()
}
getMyLocation() {
const location = window.navigator && window.navigator.geolocation
if (location) {
location.getCurrentPosition(pos => {
this.setState({latitude: pos.coords.latitude, longitude: pos.coords.longitude})
}, (error) => {
this.setState({latitude: 'err-latitude', longitude: 'err-longitude'})
})
}
}
render() {
const {latitude, longitude} = this.state;
return (
<div>
<MyWeatherData lat={latitude} lon={longitude}/>
</div>
)
}
}
import React, {Component} from "react";
import axios from "axios";
export default class MyWeatherData extends Component {
constructor() {
super();
this.state = {
descriptionMain: "",
description: "",
temperature: null,
weatherIcon: "",
name: ""
}
}
componentDidUpdate = () => {
this.getMyWeather();
}
getMyWeather = () => {
const lat = this.props.lat;
const lon = this.props.lon;
const API_KEY = "e6f4d816d3ade705ec1d8d9701b61e14";
const weatherURL = `https://api.openweathermap.org/data/2.5/weather?APPID=${API_KEY}&units=metric&lat=${lat}&lon=${lon}`;
axios
.get(weatherURL)
.then(res => {
this.setState({descriptionMain: res.data.weather[0].main, description: res.data.weather[0].description, temperature: res.data.main.temp, weatherIcon: res.data.weather[0].icon, name: res.data.name});
})
.catch(error => {
console.log(error);
});
}
render() {
const {descriptionMain, description, temperature, weatherIcon, name} = this.state;
return (
<div>
<h2>Weather for: {name}</h2>
<h4>Sky: {description}</h4>
<h5>Description: {descriptionMain}</h5>
<span className="temperature">{temperature}
°C</span>
{weatherIcon
? (<img
src={`http://openweathermap.org/img/w/${weatherIcon}.png`}
alt={`${description}`}/>)
: null}
</div>
)
}
}
<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>

When getting the information in componentDidMount in the childComponent, you props are passed but since the componentDidMount of the parent is not run by that time the updated props are not available, you should add that part of the code in componentWillReceiveProps which is executed whenever the props change or the parent component rerenders
import React, {Component} from "react";
import MyWeatherData from "./MyWeatherData";
export default class MyWeather extends Component {
constructor() {
super();
this.state = {
latitude: "",
longitude: ""
}
this.getMyLocation = this
.getMyLocation
.bind(this)
}
componentDidMount() {
this.getMyLocation()
}
getMyLocation() {
const location = window.navigator && window.navigator.geolocation
if (location) {
location.getCurrentPosition(pos => {
this.setState({latitude: pos.coords.latitude, longitude: pos.coords.longitude})
}, (error) => {
this.setState({latitude: 'err-latitude', longitude: 'err-longitude'})
})
}
}
render() {
const {latitude, longitude} = this.state;
return (
<div>
<MyWeatherData lat={latitude} lon={longitude}/>
</div>
)
}
}
import React, {Component} from "react";
import axios from "axios";
export default class MyWeatherData extends Component {
constructor() {
super();
this.state = {
descriptionMain: "",
description: "",
temperature: null,
weatherIcon: "",
name: ""
}
}
componentWillReceiveProps(nextProps){
if(nextProps.lat !== this.props.lat || nextProps.lon !== this.props.lon) {
this.getMyWeather(nextProps);
}
}
getMyWeather = (props) => {
const lat = props.lat;
const lon = props.lon;
const API_KEY = "e6f4d816d3ade705ec1d8d9701b61e14";
const weatherURL = `https://api.openweathermap.org/data/2.5/weather?APPID=${API_KEY}&units=metric&lat=${lat}&lon=${lon}`;
axios
.get(weatherURL)
.then(res => {
this.setState({descriptionMain: res.data.weather[0].main, description: res.data.weather[0].description, temperature: res.data.main.temp, weatherIcon: res.data.weather[0].icon, name: res.data.name});
})
.catch(error => {
console.log(error);
});
}
render() {
const {descriptionMain, description, temperature, weatherIcon, name} = this.state;
return (
<div>
<h2>Weather for: {name}</h2>
<h4>Sky: {description}</h4>
<h5>Description: {descriptionMain}</h5>
<span className="temperature">{temperature}
°C</span>
{weatherIcon
? (<img
src={`http://openweathermap.org/img/w/${weatherIcon}.png`}
alt={`${description}`}/>)
: null}
</div>
)
}
}

Related

React: why state or props null on page refresh?

I am displaying a tree view on left frame of the page. Tree is generating from xml file. On the click of each node, components are opening in the right frame of the page. ProductsTreeView is the tree component, Add_Category is the component that will open on the click of one of the tree node. I am passing the props through routing. everything is working fine as long as the page not refresh. In case of page refresh, props is showing null in the Add_Category page. Please help how to fix this.
[1]: https://i.stack.imgur.com/QeYB6.gif
App.js
import React, { Component } from 'react';
import { Switch, Route, BrowserRouter, Redirect } from "react-router-dom";
import Home from './components/Home';
export class App extends Component {
render() {
return (
<div>
<BrowserRouter>
<Switch>
<Route path="/" component={Home} />
<Redirect to="/" />
</Switch>
</BrowserRouter>
</div>
);
}
}
export default App;
_____________
Home.js
import React from 'react';
import { Route} from "react-router-dom";
import ProductsTree from '.ProductsTreeView';
import AddCategory from './Add_Category';
class Home extends React.Component
constructor(props) {
super(props);
this.state =
{
currentNode: {},
data: "",
};
this.setCurrentNode = this.setCurrentNode.bind(this);
}
setCurrentNode(node) {
this.setState({ currentNode: node });
}
render() {
return (
<div>
<table className="Container">
<tbody><tr width="100%">
<td className="TreeContainer">
<ProductsTree setCurrentNode={this.setCurrentNode} /> </td>
<td className="BodyContainer">
<Route path="/Add_Category">
<AddCategory key_id={this.state.currentNode.key_id} />
</Route>
</td> </tr> </tbody> </table>
</div>
);
}
}
export default Home;
_________________________
***Add_Category***
import React from 'react'
export class Add_Category extends React.Component {
constructor(props) {
super(props);
this.state = {
ID: "",
Name: "",
};
}
componentDidMount() {
if (typeof this.props.key_id !== 'undefined') {
const ID= this.props.key_id;
this.getName(ID);
}
}
componentDidUpdate(prevProps) {
if (prevProps.key_id !== this.props.key_id) {
console.log(`key_id: ${this.props.key_id}`);
const ID= this.props.key_id;
this.getName(ID);
}
}
async getName(ID) {
await fetch(REQUEST_URL)
.then(response => response.json())
.then((data) => {
this.setState({
Name: data,
ID: this.props.key_id,
loading: false})
console.log(this.state.Name)
})[![enter image description here][1]][1]
}
render() {
return (
<div>
<form>
{this.state.Name}
</form>
</div>
);
}
}
export default Add_Category;
_________________________
ProductsTreeView.js
import React, { Component } from 'react';
import ReactHtmlParser from 'react-html-parser';
import axios from 'axios';
import XMLParser from 'react-xml-parser';
import { Link } from "react-router-dom";
class ProductsTreeView extends Component {
render() {
return (
<div id="TreeView">
<TreeView setCurrentNode={this.props.setCurrentNode} />
</div>
);
}
}
class Node {
description = 'n/a';
id = -1;
key_id = -1;
linkpagename = '';
isActive = false;
nodes = [];
constructor(description, id, key_id, linkpagename) {
this.description = description;
this.id = id;
this.key_id = key_id;
this.linkpagename = linkpagename;
}
static nodesFromXml(xml) {
const map = (entity, nodes) => {
const id = entity.attributes['id'];
const key_id = entity.attributes['key-id'];
const descriptionText =
entity.children[
entity.children.findIndex((child) => child.name === 'description')
].value;
const entities = entity.children.filter(
(child) => child.name === 'entity'
);
var linkPageName = entity.attributes['link-page-name'];
linkPageName = linkPageName.replace(".aspx", "");
const node = new Node(descriptionText, id, key_id, linkPageName);
nodes.push(node);
entities.forEach((entity) => map(entity, node.nodes));
};
const parsedData = new XMLParser().parseFromString(xml);
const entities = parsedData.children.filter(
(child) => child.name === 'entity'
);
const nodes = [];
entities.forEach((entity) => map(entity, nodes));
return nodes;
}
}
class TreeView extends React.Component {
constructor(props) {
super(props);
this.state = { nodes: [] };
this.toggleNode = this.toggleNode.bind(this);
}
componentDidMount() {
axios
.get(REQUEST_URL, { 'Content-Type': 'application/xml; charset=utf-8' })
.then((response) =>
this.setState({ nodes: Node.nodesFromXml(response.data) }))
.catch(function (error) {
if (error.response) {
// Request made and server responded
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
// The request was made but no response was received
console.log(error.request);
} else {
// Something happened in setting up the request that triggered an Error
console.log('Error', error.message);
}
});
}
render() {
const nodes = this.state.nodes;
return (
<ul>
{nodes.map((node) => (
<TreeNode
id={node.id}
key={node.key_id}
node={node}
onToggle={this.toggleNode}
setCurrentNode={this.props.setCurrentNode}
/>
))}
</ul>
);
}
toggleNode(node) {
this.props.setCurrentNode(node);
function _toggleNode(currentNode, node) {
if (currentNode.id === node.id) { //currentNode.id === node.id)
{
if (currentNode.key_id === node.key_id)
{
currentNode.isActive = !currentNode.isActive;
}
}
}
else
{
currentNode.nodes.forEach((childNode) => _toggleNode(childNode, node));
}
}
const nodes = this.state.nodes;
nodes.forEach((currentNode) => _toggleNode(currentNode, node));
this.setState((state) => (state.nodes = nodes));
}
}
class TreeNode extends React.Component {
render() {
const node = this.props.node;
const onToggle = this.props.onToggle;
let activeChildren = null;
if (node.isActive && node.nodes.length > 0) {
activeChildren = (
<ul>
{node.nodes.map((node) => (
<TreeNode
id={node.id}
key={node.key_id}
node={node}
onToggle={onToggle}
/>
))}
</ul>
);
}
return (
<li
id={node.id} linkpagename={node.linkpagename}
key={node.key_id}
onClick={(event) => {
event.stopPropagation();
onToggle(node);
}}
>
<Link to={node.linkpagename} style={{ textDecoration: 'none', color: '#000000' }} >
{node.description}</Link>
{activeChildren}
</li>
);
}
}
export default ProductsTreeView;
thanks
React state is ephemeral, it lives in memory. When the page is reloaded the app is reloaded. Anything in state is reset.
This appears to be a case of needing to persist the React state to longer-term storage so when the page is reloaded the state can be reinitialized.
Here's an example:
const initialState = {
currentNode: {},
data: "",
};
class Home extends React.Component
constructor(props) {
super(props);
this.state = initialState;
this.setCurrentNode = this.setCurrentNode.bind(this);
}
componentDidMount() {
// initialize from storage
this.setState(JSON.parse(localStorage.getItem("homeState")) ?? initialState);
}
componentDidUpdate() {
// persist updates to storage
localStorage.setItem("homeState", JSON.stringify(this.state));
}
setCurrentNode(node) {
this.setState({ currentNode: node });
}
render() {
return (
<div>
<table className="Container">
<tbody>
<tr width="100%">
<td className="TreeContainer">
<ProductsTree setCurrentNode={this.setCurrentNode} />
</td>
<td className="BodyContainer">
<Route path="/Add_Category">
<AddCategory key_id={this.state.currentNode.key_id} />
</Route>
</td>
</tr>
</tbody>
</table>
</div>
);
}
}
If other components have state that needs to persist through page reloads they will also need to do this.
AddCategory
const initialState = {
ID: "",
Name: "",
}
const getStorageKey = (id) => `addCategory-${id}`;
export class AddCategory extends React.Component {
constructor(props) {
super(props);
this.state = initialState;
}
componentDidMount() {
const { key_id } = this.props;
// initialize from storage
this.setState(
JSON.parse(localStorage.getItem(getStorageKey(key_id))) ?? initialState
);
if (typeof key_id !== 'undefined') {
this.getName(key_id);
}
}
componentDidUpdate(prevProps, prevState) {
const { ID } = this.state;
const { key_id } = this.props;
if (prevProps.key_id !== key_id) {
console.log(`key_id: ${key_id}`);
this.getName(key_id);
}
if (prevState.ID !== ID) {
// persist updates to storage
localStorage.setItem(getStorageKey(ID), JSON.stringify(this.state));
}
}
async getName(ID) {
const response await fetch(REQUEST_URL);
const data = await response.json();
this.setState({
Name: data,
ID,
loading: false
});
}
render() {
...
}
}
TreeView
class TreeView extends React.Component {
constructor(props) {
super(props);
this.state = { nodes: [] };
this.toggleNode = this.toggleNode.bind(this);
}
componentDidMount() {
// initialize from storage
const storedState = JSON.parse(localStorage.getItem("treeview"));
if (storedState) {
this.setState(storedState);
} else {
axios.get(REQUEST_URL, { 'Content-Type': 'application/xml; charset=utf-8' })
.then((response) => {
this.setState(
{ nodes: Node.nodesFromXml(response.data) },
() => {
// persist updated state to storage
localStorage.setItem("treeview", JSON.stringify(this.state));
}
);
})
.catch(function (error) {
...
});
}
}
...
}

How to Jest test use of lodash.get in React component?

Error
TypeError: Cannot read property 'length' of undefined
My App component is making use of import get from 'lodash.get' https://lodash.com/docs/4.17.11#get
I'm using get inside my render function like so:
const getLabel = (listings, label) => {
const componentsMap = {
Deliveries: Delivery,
Dispensaries: Dispensary,
Doctors: Doctor
};
const DynamicIcon = componentsMap[label];
if (get(listings, 'listings').length) {
return (
<div key={label}>
<DynamicIcon fill={DARK_GRAY} /> <strong> {label} </strong>
</div>
);
}
return <div />;
};
App.test.js
import React from 'react'
import { shallow } from 'enzyme'
import toJson from 'enzyme-to-json'
import { AppJest } from './App'
import listingsMock from '../__test__/mocks/listings-mock.json';
// Mock the services.
const mockLocate = jest.fn();
const mockDisplayListing = jest.fn();
jest.mock('../actions', () => ({
locate: () => mockLocate(),
displayListing: () => mockDisplayListing()
}));
describe('<App /> component', () => {
describe('when rendering', () => {
const wrapper = shallow(<AppJest
listings={listingsMock}
locate={mockLocate}
displayListing={mockDisplayListing}
/>);
it('should render a component matching the snapshot', () => {
const tree = toJson(wrapper);
expect(tree).toMatchSnapshot();
expect(wrapper).toHaveLength(1);
});
});
});
I assumed it was because I wasn't mocking listings and passing it into the props of the shallow wrapper, but I added the mock.
listings-mock.json
{
"bottom_right": {
"latitude": 32.618865,
"longitude": -96.555516
},
"id": 1390,
"latitude": 32.78143692016602,
"listings": [
{
"avatar_image": {
"small_url": "https://images.weedmaps.com/deliveries/000/028/448/avatar/square_fill/1510581750-1507658638-Knox_Medical_Logo.png"
},
"city": "Dallas",
"distance": 2,
"id": 28448,
"license_type": "medical",
"name": "Knox Medical (Delivery Now Available)",
"online_ordering": {
"enabled_for_pickup": false,
"enabled_for_delivery": false
},
"package_level": "listing_plus",
"rating": 5,
"region_id": 1390,
"retailer_services": [
"delivery"
],
"slug": "knox-medical-dallas",
"state": "TX",
"static_map_url": "https://staticmap.weedmaps.com/static_map/13/32.7736/-96.795108/402/147/map.png",
"wmid": 459977538
}
],
"longitude": -96.7899169921875,
"name": "Dallas",
"region_path": "united-states/texas/dallas",
"slug": "dallas",
"top_left": {
"latitude": 33.016492,
"longitude": -96.999319
}
}
App.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import get from 'lodash.get';
import { locate, displayListing } from '../actions';
import Header from './header';
import Hero from './hero';
import Ripple from './partials/ripple';
import ListingCards from './listing_cards';
import Delivery from '../icons/delivery';
import Dispensary from '../icons/dispensary';
import Doctor from '../icons/doctor';
import { DARK_GRAY } from '../constants/colors';
import {
AppWrapper,
AppContent,
ListingGroups,
} from './styles';
const regionTypes = ['delivery', 'dispensary', 'doctor'];
const regionLabels = {
delivery: 'Deliveries',
dispensary: 'Dispensaries',
doctor: 'Doctors',
};
export class App extends Component {
constructor(props) {
super(props);
this.state = {
loadingTimer: 0,
isLocatingStarted: false,
geoCoords: null,
width: 0
};
this.locateMe = this.locateMe.bind(this);
this.gotoListing = this.gotoListing.bind(this);
}
componentDidMount() {
// Fetch geolocation ahead of time.
navigator.geolocation.getCurrentPosition(position =>
this.setState({ geoCoords: position.coords }));
this.updateWindowDimensions();
window.addEventListener("resize", this.updateWindowDimensions);
}
componentWillUnmount() {
window.removeEventListener("resize", this.updateWindowDimensions);
}
updateWindowDimensions = () => this.setState({ width: window.innerWidth });
locateMe() {
console.log('locateMe')
const { dispatch } = this.props;
const { geoCoords } = this.state;
if (navigator.geolocation && !geoCoords) {
navigator.geolocation.getCurrentPosition(position =>
dispatch(locate(position.coords)));
} else {
dispatch(locate(geoCoords));
}
this.setState({ isLocatingStarted: true });
};
gotoListing(listing) {
const { dispatch } = this.props;
dispatch(displayListing(listing));
const link = `/listing/${listing.wmid}`;
this.props.history.push(link);
}
render() {
const { isLocating, location, regions, error } = this.props;
const { isLocatingStarted, width } = this.state;
const { state_abv: state } = location !== null && location;
const isLoading = isLocatingStarted && isLocating;
const getLabel = (listings, label) => {
const componentsMap = {
Deliveries: Delivery,
Dispensaries: Dispensary,
Doctors: Doctor
};
const DynamicIcon = componentsMap[label];
if (get(listings, 'listings').length) {
return (
<div key={label}>
<DynamicIcon fill={DARK_GRAY} /> <strong> {label} </strong>
</div>
);
}
return <div />;
};
return (
<AppWrapper>
<Header history={this.props.history} />
<Hero
location={location}
isLocating={isLocating}
locateMe={this.locateMe}
/>
{ isLoading ? <Ripple /> :
<AppContent>
{error && <div> {error.message} </div>}
{regions && (
<React.Fragment>
{regionTypes.map(regionType => (
<ListingGroups key={regionType}>
<h2>
{getLabel(regions[regionType], regionLabels[regionType])}
</h2>
<ListingCards
listings={get(regions[regionType], 'listings')}
state={state}
isMobileSize={width < 769}
gotoListing={this.gotoListing}
/>
</ListingGroups>
))}
</React.Fragment>
)}
</AppContent>
}
</AppWrapper>
);
}
}
const mapStateToProps = state => state.location;
App.propTypes = {
isLocating: PropTypes.bool.isRequired,
location: PropTypes.object,
regions: PropTypes.object,
dispatch: PropTypes.any,
error: PropTypes.object,
};
App.defaultProps = {
isLocating: false,
location: {},
regions: {},
error: {},
};
export const AppJest = App
export default connect(mapStateToProps)(App);
Ah I needed to finish adding in all the props to the wrapper component:
import React from 'react'
import { shallow } from 'enzyme'
import toJson from 'enzyme-to-json'
import { AppJest } from './App'
import Header from './header';
import Hero from './hero';
import Ripple from './partials/ripple';
import listingsMock from '../__test__/mocks/listings-mock.json';
// Mock the services.
const mockLocate = jest.fn();
const mockDisplayListing = jest.fn();
const mockGeolocation = {
getCurrentPosition: jest.fn(),
watchPosition: jest.fn()
};
global.navigator.geolocation = mockGeolocation;
jest.mock('../actions', () => ({
locate: () => mockLocate(),
displayListing: () => mockDisplayListing()
}));
describe('<App /> component', () => {
describe('when rendering', () => {
const wrapper = shallow(<AppJest
navigator={mockGeolocation}
isLocating={false}
location={null}
regions={null}
dispatch={null}
error={null}
listings={listingsMock}
locate={mockLocate}
displayListing={mockDisplayListing}
/>);
it('should render a component matching the snapshot', () => {
const tree = toJson(wrapper);
expect(tree).toMatchSnapshot();
expect(wrapper).toHaveLength(1);
expect(wrapper.find(Header)).toHaveLength(1);
expect(wrapper.find(Hero)).toHaveLength(1);
});
});
});
Required props:
App.propTypes = {
isLocating: PropTypes.bool.isRequired,
location: PropTypes.object,
regions: PropTypes.object,
dispatch: PropTypes.any,
error: PropTypes.object,
};
App.defaultProps = {
isLocating: false,
location: {},
regions: {},
error: {},
};

How to target specific element after mapping and passing onClick function as props

I am facing such problem, i got my array of records fetched from an API, mapped it into single elements and outputting them as single components. I have function which changes state of parent Component, passes value to child component and child component should hide/show div content after button is clicked.
Of course. It is working, but partially - my all divs are being hidden/shown. I have set specific key to each child component but it doesn't work.
App.js
import React, { Component } from 'react';
import './App.css';
import axios from 'axios';
import countries from '../../countriesList';
import CitySearchForm from './CitySearchForm/CitySearchForm';
import CityOutput from './CityOutput/CityOutput';
import ErrorMessage from './ErrorMessage/ErrorMessage';
class App extends Component {
state = {
country: '',
error: false,
cities: [],
infoMessage: '',
visible: false
}
getCities = (e) => {
e.preventDefault();
const countryName = e.target.elements.country.value.charAt(0).toUpperCase() + e.target.elements.country.value.slice(1);
const countryUrl = 'https://api.openaq.org/v1/countries';
const wikiUrl ='https://en.wikipedia.org/w/api.php?action=query&prop=extracts&exintro&explaintext&format=json&category=city&redirects&origin=*&titles=';
const allowedCountries = new RegExp(/spain|germany|poland|france/, 'i');
if (allowedCountries.test(countryName)) {
axios
.get(countryUrl)
.then( response => {
const country = response.data.results.find(el => el.name === countryName);
return axios.get(`https://api.openaq.org/v1/cities?country=${country.code}&order_by=count&sort=desc&limit=10`)
})
.then( response => {
const cities = response.data.results.map(record => {
return { name: record.city };
});
cities.forEach(city => {
axios
.get(wikiUrl + city.name)
.then( response => {
let id;
for (let key in response.data.query.pages) {
id = key;
}
const description = response.data.query.pages[id].extract;
this.setState(prevState => ({
cities: [...prevState.cities, {city: `${city.name}`, description}],
infoMessage: prevState.infoMessage = ''
}))
})
})
})
.catch(error => {
console.log('oopsie, something went wrong', error)
})
} else {
this.setState(prevState => ({
infoMessage: prevState.infoMessage = 'This is demo version of our application and is working only for Spain, Poland, Germany and France',
cities: [...prevState.cities = []]
}))
}
}
descriptionTogglerHandler = () => {
this.setState((prevState) => {
return { visible: !prevState.visible};
});
};
render () {
return (
<div className="App">
<ErrorMessage error={this.state.infoMessage}/>
<div className="form-wrapper">
<CitySearchForm getCities={this.getCities} getInformation={this.getInformation} countries={countries}/>
</div>
{this.state.cities.map(({ city, description }) => (
<CityOutput
key={city}
city={city}
description={description}
show={this.state.visible}
descriptionToggler={this.descriptionTogglerHandler} />
))}
</div>
);
}
}
export default App;
CityOutput.js
import React, { Component } from 'react';
import './CityOutput.css';
class CityOutput extends Component {
render() {
const { city, descriptionToggler, description, show } = this.props;
let descriptionClasses = 'output-record description'
if (show) {
descriptionClasses = 'output-record description open';
}
return (
<div className="output">
<div className="output-record"><b>City:</b> {city}</div>
<button onClick={descriptionToggler}>Read more</button>
<div className={descriptionClasses}>{description}</div>
</div>
)
}
};
export default CityOutput;
Put the visible key and the toggle function in the CityOutput instead of having it in the parent
import React, { Component } from "react";
import "./CityOutput.css";
class CityOutput extends Component {
state = {
visible: true
};
descriptionTogglerHandler = () => {
this.setState({ visible: !this.state.visible });
};
render() {
const { city, description } = this.props;
let descriptionClasses = "output-record description";
if (this.state.visible) {
descriptionClasses = "output-record description open";
}
return (
<div className="output">
<div className="output-record">
<b>City:</b> {city}
</div>
<button onClick={() => this.descriptionTogglerHandler()}>Read more</button>
<div className={descriptionClasses}>{description}</div>
</div>
);
}
}
export default CityOutput;
There are two ways of how I would approach this,
The first one is setting in your state a key property and check and compare that key with the child keys like:
state = {
country: '',
error: false,
cities: [],
infoMessage: '',
visible: false.
currKey: 0
}
descriptionTogglerHandler = (key) => {
this.setState((prevState) => {
return { currKey: key, visible: !prevState.visible};
});
};
// then in your child component
class CityOutput extends Component {
render() {
const { city, descriptionToggler, description, show, currKey, elKey } = this.props;
let descriptionClasses = 'output-record description'
if (show && elKey === currKey) {
descriptionClasses = 'output-record description open';
}
return (
<div className="output">
<div className="output-record"><b>City:</b> {city}</div>
<button onClick={() => descriptionToggler(elKey)}>Read more</button>
<div className={descriptionClasses}>{description}</div>
</div>
)
}
};
The other way is to set an isolated state for every child component
class CityOutput extends Component {
constructor(props) {
this.state = {
show: false
}
}
function descriptionToggler() {
const {show} = this.state;
this.setState({
show: !show
})
}
render() {
const { city, descriptionToggler, description } = this.props;
let descriptionClasses = 'output-record description'
if (this.state.show) {
descriptionClasses = 'output-record description open';
}
return (
<div className="output">
<div className="output-record"><b>City:</b> {city}</div>
<button onClick={descriptionToggler}>Read more</button>
<div className={descriptionClasses}>{description}</div>
</div>
)
}
};
I hope this helps ;)

React draft wysiwyg - Can't able to type text in editor after clearing editorState

I'm trying to reset editor content after some action completed using react-draft-wysiwyg editor. All contents cleared by using clearEditorContent method from draftjs-utils. But after clearing contents I can't able to type nothing in editor. Added the code below. Please help to solve this problem.
import React, { Component } from 'react';
import { EditorState, convertToRaw, ContentState } from 'draft-js';
import { clearEditorContent } from 'draftjs-utils'
import { Editor } from 'react-draft-wysiwyg';
import '../../../../node_modules/react-draft-wysiwyg/dist/react-draft-wysiwyg.css';
import draftToHtml from 'draftjs-to-html';
import htmlToDraft from 'html-to-draftjs';
export default class RTEditor extends Component {
constructor(props) {
super(props);
this.state = {
editorState: EditorState.createEmpty(),
};
this.setDomEditorRef = ref => this.domEditor = ref;
}
onEditorStateChange: Function = (editorState) => {
this.setState({
editorState,
}, () => {
this.props.sendResult(draftToHtml(convertToRaw(this.state.editorState.getCurrentContent())));
});
};
componentWillReceiveProps(nextProps) {
if(nextProps.reset) {
this.reset();
}
}
componentDidMount() {
if(this.props.text) {
const html = `${this.props.text}`;
const contentBlock = htmlToDraft(html);
if (contentBlock) {
const contentState = ContentState.createFromBlockArray(contentBlock.contentBlocks);
const editorState = EditorState.createWithContent(contentState);
this.setState({ editorState, });
}
}
this.domEditor.focusEditor();
}
reset = () => {
let {editorState} = this.state;
editorState = clearEditorContent(editorState);
this.setState({ editorState });
};
render() {
const { editorState } = this.state;
return (
<Editor
ref={this.setDomEditorRef}
editorState={editorState}
wrapperClassName="rte-wrapper"
editorClassName="rte-editor"
onEditorStateChange={this.onEditorStateChange}
toolbarCustomButtons={[this.props.UploadHandler]}
/>
)
}
}
My parent component code is below,
import React, { Component } from 'react'
import { addThreadPost } from '../../../api/thread-api'
import { isEmpty } from '../../../api/common-api'
import RTEditor from './Loadable'
export default class ThreadActivity extends Component {
constructor(props) {
super(props)
this.state = {
clearEditor: false,
threadPost: ''
}
}
setPost = (post) => {
this.setState({ threadPost: post })
}
addThreadPost = () => {
let postText = this.state.threadPost.replace(/<[^>]+>/g, '');
if(!isEmpty(postText)) {
addThreadPost(this.props.match.params.id_thread, this.state.threadPost, this.state.postAttachments).then(response => {
this.setState({
clearEditor: true,
postAttachments: [],
})
});
}
else {
alert("Please enter some text in box.");
}
}
render() {
return [
<div className="commentbox-container">
<div className="form-group">
<div className="form-control">
<RTEditor
ref={node => { this.threadPost = node }}
text={""}
sendResult={this.setPost.bind(this)}
reset={this.state.clearEditor}
/>
<button className="btn-add-post" onClick={this.addThreadPost}>Add Post</button>
</div>
</div>
</div>
]
}
}
Your problem is probably that once you set ThreadActivity's state.clearEditor to true, you never set it back to false. So your this.reset() is getting called every time the component receives props. Which, incidentally, is going to be every time you try to type because you're invoking that this.props.sendResult.
The simplest fix is to make sure you change state.clearEditor back to false once the clearing is done.
Add to ThreadActivity.js:
constructor(props) {
...
this.completeClear = this.completeClear.bind(this);
}
...
completeClear = () => {
this.setState({clearEditor: false});
}
render() {
...
<RTEditor
...
completeClear={this.completeClear}
/>
...
}
And in RTEditor.js:
reset = () => {
let {editorState} = this.state;
editorState = clearEditorContent(editorState);
this.setState({ editorState });
this.props.completeClear();
};

Re-render Header with different icon - React Native

I need help, I have a component, and your function is render de header of app, with right and left icon, and in center is title of current page.
But, I can re-render the title of page, but the icon not re-render.
I not have idea for the solution this.
MyCode of Header.
import React, { Component } from 'react';
import ReactNative from 'react-native';
import { Icon, Text } from './../labsoft.ui';
import styles from './styles';
const Header = ReactNative.StyleSheet.flatten(styles.header);
const BoxHeaderFlex = ReactNative.StyleSheet.flatten(styles.boxHeaderFlex);
const BoxHeaderIcon = ReactNative.StyleSheet.flatten(styles.boxHeaderIcon);
const BoxHeaderTouchable = ReactNative.StyleSheet.flatten(styles.BoxHeaderTouchable);
const BoxHeaderTouchableCenter = ReactNative.StyleSheet.flatten(styles.BoxHeaderTouchableCenter);
interface HeaderProperties {
leftAction?: HeaderLeftAction,
rightAction?: HeaderRightAction,
title?: string;
style?: Style;
}
interface HeaderState {
leftAction?: HeaderLeftAction,
rightAction?: HeaderRightAction,
title?: string;
}
interface HeaderLeftAction {
icon: string;
onClick?: () => void
}
interface HeaderRightAction {
icon: string;
onClick?: () => void
}
interface Style { }
export default class HeaderComponent extends Component<HeaderProperties, HeaderState> {
constructor(props: HeaderProperties) {
super(props);
this.state = {
leftAction: this.props.leftAction,
rightAction: this.props.rightAction,
title: this.props.title
}
}
public setLeftAction(action: HeaderLeftAction) {
this.setState({
leftAction: action
});
}
public setRightAction(action: HeaderRightAction) {
this.setState({
rightAction: action
});
}
public setTitle(title: string) {
this.setState({
title: title
});
}
render() {
console.log('props: ', this.props.rightAction.icon);
console.log('state: ', this.state.rightAction.icon);
let iconRight = this.state.rightAction.icon;
let iconLeft = this.state.leftAction.icon;
return (
<ReactNative.View style={[BoxHeaderFlex, { ...this.props.style }]}>
{
this.state.leftAction != null ?
<Icon icon={iconLeft} onPress={this.state.leftAction.onClick} />
:
<ReactNative.TouchableOpacity style={BoxHeaderTouchable}>
<ReactNative.View>
</ReactNative.View>
</ReactNative.TouchableOpacity>
}
{
this.state.title != null ?
<Text style={BoxHeaderTouchableCenter}>{this.state.title}</Text>
:
<Text style={BoxHeaderTouchableCenter} />
}
{
this.state.rightAction != null ?
<Icon icon={iconRight} onPress={this.state.rightAction.onClick} />
:
<ReactNative.TouchableOpacity style={BoxHeaderTouchable}>
<ReactNative.View>
</ReactNative.View>
</ReactNative.TouchableOpacity>
}
</ReactNative.View >
);
}
}
My request for the change icon in other page (example: geolocation)
import React, { Component } from 'react';
import ReactNative from 'react-native';
import { styles, Container, Text } from './labsoft/labsoft.ui';
import App from "./app";
import { BasicPageProperties, BasicPageState, BasicPage } from './interfaces/generics/basicPage';
export interface GeolocationPageProperties extends BasicPageProperties {
}
export interface GeolocationPageState {
latitude: any,
longitude: any,
address: any,
error: any
}
export default class GeolocationPage extends BasicPage<GeolocationPageProperties, GeolocationPageState> {
constructor(props: GeolocationPageProperties) {
super(props);
this.state = {
latitude: null,
longitude: null,
address: null,
error: null,
};
}
componentWillMount() {
this.app.header.setRightAction({
icon: 'star',
onClick: () => { }
})
}
componentDidMount() {
navigator.geolocation.getCurrentPosition(
(position) => {
console.log(position);
this.setState({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
address: "",
error: null,
});
console.log('http://maps.googleapis.com/maps/api/geocode/json?latlng=' + position.coords.latitude + ',' + position.coords.longitude + '&sensor=true');
// fetch('http://maps.googleapis.com/maps/api/geocode/json?latlng=' + position.coords.latitude + ',' + position.coords.longitude + '&sensor=true')
// .then((response) => response.json())
// .then((data) => {
// this.setState({
// latitude: position.coords.latitude,
// longitude: position.coords.longitude,
// address: data.results[0].formatted_address,
// error: null,
// });
// })
// .catch((error) => {
// console.error(error);
// });
},
(error) => this.setState({ error: error.message }),
{ enableHighAccuracy: true, timeout: 10000, maximumAge: 1000 },
);
}
render() {
return (
<Container>
<Text>Latitude: {this.state.latitude}</Text>
<Text>Longitude: {this.state.longitude}</Text>
<Text>Endereço: {this.state.address}</Text>
{this.state.error ? <Text>Error: {this.state.error}</Text> : null}
</Container>
);
}
}
And other code, for request change icon, but not working
import React, { Component } from 'react';
import ReactNative from 'react-native';
import { Container, Text, Button } from './labsoft/labsoft.ui';
import App from "./app";
import { BasicPageProperties, BasicPageState, BasicPage } from './interfaces/generics/basicPage';
export interface MainProperties extends BasicPageProperties {
}
export interface MainState extends BasicPageState {
}
export default class MainPage extends BasicPage<MainProperties, MainState> {
constructor(props: MainProperties) {
super(props);
}
render() {
return (
<Container>
<Button title="aaa" onPress={() => this.app.openDrawer()} />
<Button title="change right action"
onPress={() => {
this.app.header.setRightAction({
icon: "bars",
onClick: () => {
alert("star");
}
})
}} />
</Container>
);
}
}
When navigator render other page, i set null in header icon.
it's working

Categories

Resources