Call a function in a component from antoher file in react - reactjs

I'm learning reactjs and I'm stuck calling a function in another component.
I did:
import moment from 'moment';
import WeatherLocation from './../components/WeatherLocation'
const transformForecast = datos =>(
datos.list.filter(item => (
moment.unix(item.dt).utc().hour() === 6 ||
moment.unix(item.dt).utc().hour() === 12 ||
moment.unix(item.dt).utc().hour() === 18
)).map(item => (
{
weekDay: moment.unix(item.dt).format('ddd'),
hour: moment.unix(item.dt).hour(),
data: WeatherLocation.getDatos(item)
}
))
);
export default transformForecast;
getDatos is a function in WeatherLocation, I exported WeatherLocation but I don't know what if that calling is correct.
WeatherLocation component:
const api_key = "bb7a92d73a27a97e54ba00fab9d32063";
class WeatherLocation extends Component{
constructor({ city }){
super();
this.state = {
city,
primero: null
}
}
getWeatherState = weather => {
const { id } = weather[0];
if (id < 300){
return THUNDER;
}else if (id < 400){
return DRIZZLE;
}else if (id < 600){
return RAIN;
}else if (id < 700){
return SNOW;
}else if (id >= 800){
return SUN;
}else{
return CLOUDY;
}
};
getTemp = kelvin =>{
return convert(kelvin).from('K').to('C').toFixed(2);
}
getDatos = (weather_data) =>{
const {weather} = weather_data;
const {humidity, temp} = weather_data.main;
const {speed} = weather_data.wind;
const weatherState = this.getWeatherState(weather);
const temperature = this.getTemp(temp);
const primero = {
humidity,
temperature,
weatherState,
wind: `${speed}`,
}
return primero;
};
componentWillMount() {
this.handleUpdateClick();
}
handleUpdateClick = () => {
const {city} = this.state;
const urlTiempo = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${api_key}`;
fetch(urlTiempo).then(primero => {
return primero.json();
}).then(weather_data => {
const primero = this.getDatos(weather_data);
this.setState({primero});
});
};
render = () => {
const {onWeatherLocationClick} = this.props;
const {city, primero} = this.state;
return (
<div className='weatherLocationCont' onClick = {onWeatherLocationClick}>
<Location city={city}/>
{primero ? <WeatherData datos = {primero}/> : <CircularProgress size={60} thickness={7} />}
</div>);
};
}
WeatherLocation.propTypes = {
city: PropTypes.string.isRequired,
onWeatherLocationClick: PropTypes.func
}
export default WeatherLocation;
As you can see I want to reuse getDatos because I'm going to need those variable in transformForecast.
I will appreciate your help, thanks.

WeatherLocation is a React component, not a plain JS object, so you can't just call its internal functions as you please: as just a class definition there is nothing to call yet, you need an instance.
So, you'll need to create an actual <WeatherLocation.../> component on your page/in your UI, and then use the WeatherLocation's documented API for getting its data based on changes in the component, passing it on to whatever is calling the transformForecast function.

Object.Method() call is not allowed here. You need to create a React Stateful or Stateless component and pass props to it from the parent component. Let us say, WeatherLocation is your parent component and transformForecast is the child component. You can do something like this to call your method in WeatherLocation component.
Parent Component:
class WeatherLocation extends React.Component {
constructor(props) {
super(props);
this.state = {
datos: []
};
this.getDatos = this.getDatos.bind(this);
};
getDatos = (item) => {
console.log(item);
};
render() {
return (
<div>
<TransformForecast
getDatos={this.getDatos}
datos={this.state.datos}
/>
</div>
)
}
}
export default WeatherLocation;
Child Component:
const TransformForecast = (props) => {
return (
props.datos.list.filter(item => (
moment.unix(item.dt).utc().hour() === 6 ||
moment.unix(item.dt).utc().hour() === 12 ||
moment.unix(item.dt).utc().hour() === 18
)).map(item => (
{
weekDay: moment.unix(item.dt).format('ddd'),
hour: moment.unix(item.dt).hour(),
data: props.getDatos(item)
}
))
);
};
export default TransformForecast;
Note: This code might not be the right away working code as I'm not sure of the data and API's called. This is just to illustrate to solve your problem.
Hope this helps.

Related

Two components get called when page refreshes and the state is altered React

Basically, I have one component, let's call it component1 and a second component, which has been created by duplicating the first one called component2. I had to duplicate it, because some objects inside it had to be altered before sending them to the further components.
On one page I have an onClick event which triggers component1 which opens a modal and on another page, component2 is trigger the same as for the first one.
The problem occurs here, if I'm on the second page where the modal from component2 is opened and I refresh the page, both components are called, of course component1 is the first one called and the state is altered by this component which makes me not having the desired information in the second component.
As far as I understood, because of the fact that in both components, mapStateToProps is altering my state, both components are called. Not really sure though that I understood right.
Here is my component1 summary:
class LivePlayerModal extends React.Component {
constructor(props) {
super(props);
this.highlightsUpdated = null;
}
componentDidMount() {
const queryParam = UrlHelper.getParamFromLocation(IS_QUALIFICATION, window.location);
if (queryParam === null) {
ScoringLoader.subscribe(endpointNames.LIVE_SCANNER);
ScoringLoader.subscribe(endpointNames.PLAYERS);
ScoringLoader.subscribe(endpointNames.LEADERBOARD);
ScoringLoader.subscribe(endpointNames.COURSE);
ScoringLoader.subscribe(endpointNames.STATISTICS);
}
//TODO: make fixed fetch on timeout
this.fetchHighlights();
}
componentDidUpdate(prevProps) {
if (prevProps.playerId !== this.props.playerId) {
this.highlightsUpdated = null;
}
this.fetchHighlights();
}
componentWillUnmount() {
ScoringLoader.unsubscribe(endpointNames.LIVE_SCANNER);
ScoringLoader.unsubscribe(endpointNames.PLAYERS);
ScoringLoader.unsubscribe(endpointNames.LEADERBOARD);
ScoringLoader.unsubscribe(endpointNames.COURSE);
ScoringLoader.unsubscribe(endpointNames.STATISTICS);
}
render() {
const {
isOpen, scoringPlayer, isQualification, ...rest
} = this.props;
const highlightGroups = getHighlights(this.getCloudHighlights());
if (isQualification) {
return null;
}
return (
<ReactModal isOpen={isOpen} onCloseCb={this.hide}>
<div className="live-player">
{
scoringPlayer === undefined &&
<BlockPlaceholder minHeight={400}>
<BlockSpinner />
</BlockPlaceholder>
}
{
scoringPlayer === null &&
<LivePreMessage
model={{
title: '',
body: 'Player data coming soon'
}}
bemList={[bemClasses.LIGHT]}
/>
}
{
scoringPlayer &&
<LivePlayerLayout
{...rest}
scoringPlayer={scoringPlayer}
highlightGroups={highlightGroups}
/>
}
</div>
</ReactModal>
);
}
}
const mapStateToProps = (state, ownProps) => {
const isQualification = state.scoring.isQualification;
const { playerId } = ownProps;
const sitecorePlayers = state.scoring[endpointNames.PLAYERS];
const scoringLeaderboard = state.scoring[endpointNames.LEADERBOARD];
const getScoringPlayer = () => {
};
return ({
isQualification,
liveScanner: state.scoring[endpointNames.LIVE_SCANNER],
scoringLeaderboard,
scoringPlayer: getScoringPlayer(),
scoringStats: state.scoring[endpointNames.STATISTICS],
scoringCourse: state.scoring[endpointNames.COURSE],
sitecorePlayers: state.scoring[endpointNames.PLAYERS],
cloudMatrix: state.cloudMatrix
});
};
const mapDispatchToProps = (dispatch) => ({
fetchPlayerHighlights: (feedUrl) => dispatch(fetchFeed(feedUrl))
});
const LivePlayerCardContainer = connect(
mapStateToProps,
mapDispatchToProps
)(LivePlayerModal);
export default LivePlayerCardContainer;
Here is my component2 summary :
class QualificationLivePlayerModal extends React.Component {
constructor(props) {
super(props);
this.highlightsUpdated = null;
}
shouldComponentUpdate(nextProps) {
return nextProps.isQualification;
}
componentDidMount() {
ScoringLoader.subscribe(endpointNames.SUMMARY_FINAL);
ScoringLoader.subscribe(endpointNames.SUMMARY_REGIONAL);
ScoringLoader.subscribe(endpointNames.LIVE_SCANNER);
ScoringLoader.subscribe(endpointNames.PLAYERS);
ScoringLoader.subscribe(endpointNames.COURSE);
ScoringLoader.unsubscribe(endpointNames.LEADERBOARD);
ScoringLoader.unsubscribe(endpointNames.STATISTICS);
//TODO: make fixed fetch on timeout
this.fetchHighlights();
}
componentDidUpdate(prevProps) {
if (prevProps.playerId !== this.props.playerId) {
this.highlightsUpdated = null;
}
this.fetchHighlights();
}
componentWillUnmount() {
ScoringLoader.unsubscribe(endpointNames.SUMMARY_FINAL);
ScoringLoader.unsubscribe(endpointNames.SUMMARY_REGIONAL);
ScoringLoader.unsubscribe(endpointNames.COURSE);
ScoringLoader.unsubscribe(endpointNames.LEADERBOARD);
ScoringLoader.unsubscribe(endpointNames.STATISTICS);
}
render() {
const {
scoringPlayer, summaryFinal, ...rest
} = this.props;
const highlightGroups = getHighlights(this.getCloudHighlights());
const queryParam = UrlHelper.getParamFromLocation(IS_QUALIFICATION, window.location);
const open = (queryParam === 'true');
if (scoringPlayer !== undefined && scoringPlayer !== null) scoringPlayer.id = scoringPlayer.entryId;
return (
<ReactModal isOpen={open} onCloseCb={this.hide}>
<div className="qual-live-player">
{
scoringPlayer === undefined &&
<BlockPlaceholder minHeight={400}>
<BlockSpinner />
</BlockPlaceholder>
}
{
scoringPlayer === null &&
<LivePreMessage
model={{
title: '',
body: 'Player data coming soon'
}}
bemList={[bemClasses.LIGHT]}
/>
}
{
scoringPlayer &&
<LivePlayerLayout
{...rest}
scoringPlayer={scoringPlayer}
highlightGroups={highlightGroups}
/>
}
</div>
</ReactModal>
);
}
}
const mapStateToProps = (state, ownProps) => {
const isQualification = state.scoring.isQualification;
const { playerId, location } = ownProps;
const locationIdFromQueryParam = UrlHelper.getParamFromLocation(LOCATION_ID, window.location);
const locationId = location !== null ? location.locationId : locationIdFromQueryParam;
const sitecorePlayers = state.scoring[endpointNames.PLAYERS];
const summaryRegional = state.scoring[endpointNames.SUMMARY_REGIONAL];
const summaryFinal = state.scoring[endpointNames.SUMMARY_FINAL];
const scoringLeaderboard = getLeaderboardBasedOnLocation(locationId, summaryFinal, summaryRegional);
const currentRound = getCurrentRound(locationId, summaryFinal, summaryRegional);
const getScoringPlayer = () => {
};
return ({
isQualification,
liveScanner: state.scoring[endpointNames.LIVE_SCANNER],
scoringLeaderboard,
scoringPlayer: getScoringPlayer(),
scoringCourse: getScoringCourseFromQualificationFeed(),
sitecorePlayers: state.scoring[endpointNames.PLAYERS],
cloudMatrix: state.cloudMatrix,
});
};
const mapDispatchToProps = (dispatch) => ({
fetchPlayerHighlights: (feedUrl) => dispatch(fetchFeed(feedUrl))
});
const QualificationLivePlayerCardContainer = connect(
mapStateToProps,
mapDispatchToProps
)(QualificationLivePlayerModal);
export default QualificationLivePlayerCardContainer;
Basically, the problem i ve got here, is that in state.scoring I do not have the information for the endpoints present in the return statement of the render method before the page finishes the refresh process, which later on makes my app to break.
Hope I've been clear enough.
Is there a solution for waiting the endpoints to get called or even not loading the first component at all?

Redux - Fetch data, but render in another component

I'm currently fetching data in Component1, then dispatching an action to update the store with the response. The data can be seen in Component2 in this.props, but how can I render it when the response is returned? I need a way to reload the component when the data comes back.
Initially I had a series of functions run in componentDidMount but those are all executed before the data is returned to the Redux store from Component1. Is there some sort of async/await style between components?
class Component1 extends React.Component {
componentDidMount() {
this.retrieveData()
}
retrieveData = async () => {
let res = await axios.get('url')
updateParam(res.data) // Redux action creator
}
}
class Component2 extends React.Component {
componentDidMount() {
this.sortData()
}
sortData = props => {
const { param } = this.props
let result = param.sort((a,b) => a - b)
}
}
mapStateToProps = state => {
return { param: state.param }
}
connect(mapStateToProps)(Component2)
In Component2, this.props is undefined initially because the data has not yet returned. By the time it is returned, the component will not rerender despite this.props being populated with data.
Assuming updateParam action creator is correctly wrapped in call to dispatch in mapDispatchToProps in the connect HOC AND properly accessed from props in Component1, then I suggest checking/comparing props with previous props in componentDidUpdate and calling sortData if specifically the param prop value updated.
class Component2 extends React.Component {
componentDidMount() {
this.sortData()
}
componentDidUpdate(prevProps) {
const { param } = this.props;
if (prevProps.param !== param) { // <-- if param prop updated, sort
this.sortData();
}
}
sortData = () => {
const { param } = this.props
let result = param.sort((a, b) => a - b));
// do something with result
}
}
mapStateToProps = state => ({
param: state.param,
});
connect(mapStateToProps)(Component2);
EDIT
Given component code from repository
let appointmentDates: object = {};
class Appointments extends React.Component<ApptProps> {
componentDidUpdate(prevProps: any) {
if (prevProps.apptList !== this.props.apptList) {
appointmentDates = {};
this.setAppointmentDates();
this.sortAppointmentsByDate();
this.forceUpdate();
}
}
setAppointmentDates = () => {
const { date } = this.props;
for (let i = 0; i < 5; i++) {
const d = new Date(
new Date(date).setDate(new Date(date).getDate() + i)
);
let month = new Date(d).toLocaleString("default", {
month: "long"
});
let dateOfMonth = new Date(d).getDate();
let dayOfWeek = new Date(d).toLocaleString("default", {
weekday: "short"
});
// #ts-ignore
appointmentDates[dayOfWeek + ". " + month + " " + dateOfMonth] = [];
}
};
sortAppointmentsByDate = () => {
const { apptList } = this.props;
let dates: string[] = [];
dates = Object.keys(appointmentDates);
apptList.map((appt: AppointmentQuery) => {
return dates.map(date => {
if (
new Date(appt.appointmentTime).getDate().toString() ===
// #ts-ignore
date.match(/\d+/)[0]
) {
// #ts-ignore
appointmentDates[date].push(appt);
}
return null;
});
});
};
render() {
let list: any = appointmentDates;
return (
<section id="appointmentContainer">
{Object.keys(appointmentDates).map(date => {
return (
<div className="appointmentDateColumn" key={date}>
<span className="appointmentDate">{date}</span>
{list[date].map(
(apptInfo: AppointmentQuery, i: number) => {
return (
<AppointmentCard
key={i}
apptInfo={apptInfo}
/>
);
}
)}
</div>
);
})}
</section>
);
}
}
appointmentDates should really be a local component state object, then when you update it in a lifecycle function react will correctly rerender and you won't need to force anything. OR since you aren't doing anything other than computing formatted data to render, Appointments should just call setAppointmentDates and sortAppointmentsByDate in the render function.

Trying to use function in a react stateless class component

I am trying to build a react app for weather. I am using openweathermap api.
i have a component that supposed to render 5 weather components, each one for each day (it's a 5 day forecast).
however, something is not working.
this is the weather component:
class Weather extends Component{
getAllDates = () =>{
const list = this.props.list;
let date = list[0]["dt_txt"].split(" ")[0];
const datesList = [{date: date, indexes: [0]}];
let indexes = [];
for(let i = 1; i < list.length; i++){
const currDate = list[i]["dt_txt"].split(" ")[0];
if(date !== currDate){
datesList.push({
date: currDate,
indexes: indexes});
date = currDate;
indexes = [];
}
else{
const toUpdate = datesList.pop();
const oldIndexes = toUpdate.indexes;
const newIndexes = oldIndexes.push(i);
toUpdate.indexes = newIndexes;
datesList.push(toUpdate);
}
}
return datesList;
}
render(){
const datesList = this.getAllDates();
return(
<React.Fragment>
{datesList.map((date, key) => {
return <DayWeather date = {date}
key = {key}
forecastList = {this.props.list} />
})}
</React.Fragment>
)
}
and in App.js I am rendering it conditionally like this:
{this.state.data === undefined ? <h1>Choose City</h1> :
<Weather list = {this.state.data.list}/>}
The problem is, the getAllDates is called twice from the render function in Weather component.
the loop goes for 2 iterations instead of 40, and then getAllDates is called again. I can't understand why because I have conditional rendering in App.js.
Also, the indexes variable at some point turns into a number and not an array,
and then I get an error that I can't do .push to indexes.
I have no idea what is hapenning here,
would appreciate any kind of help!
thank you!
**here I am fetching the data:
class App extends Component{
constructor(){
super();
this.state = {
data: undefined
}
}
onSelectHandler = (event) =>{
const city = event.target.value;
const apiCall = `http://api.openweathermap.org/data/2.5/forecast?q=${city},il&APPID=${API_KEY}`
fetch(apiCall)
.then(response => response.json())
.then(response =>
{
this.setState({
data: response
})
}
)
}

React - how to determine if a specific child component exists?

If I have this structure:
const MyComponent = (props) => {
return (
<Wrapper />{props.children}</Wrapper>
);
}
and I use it like this:
<MyComponent>
<SomeInnerComponent />
</MyComponent>
How can I check to see if <SomeInnerComponent /> has specifically been included between <MyComponent></MyComponent>, from within the MyComponent function?
Given that you want to check that SomeInnerComponent is present as a child or not, you could do the following
const MyComponent = (props) => {
for (let child in props.children){
if (props.children[child].type.displayName === 'SomeInnerComponent'){
console.log("SomeInnerComponent is present as a child");
}
}
return (
<Wrapper />{props.children}</Wrapper>
);
}
Or you could have a propTypes validation on your component
MyComponent.propTypes: {
children: function (props, propName, componentName) {
var error;
var childProp = props[propName];
var flag = false;
React.Children.forEach(childProp, function (child) {
if (child.type.displayName === 'SomeInnerComponent') {
flag = true
}
});
if(flag === false) {
error = new Error(componentName + ' does not exist!'
);
}
return error;
}
},
Just want to provide an answer for a similar but different need, where you might have an HOC wrapping several layers of components, and you'd like to see if the HOC has already wrapped a component. The method I came up with was to have the HOC add a data-attribute onto the component to serve as a flag, which the HOC could then check on subsequent runs.
const WithFoo = Component = props => {
return props["data-with-foo"]
? <Component {...props} />
: (
<FooWrapper>
<Component {...props} data-with-foo />
</FooWrapper>
);
};
React nodes have a type property that's set to the constructor, or the function that created the node. So I wrote the following function for checking if a React node was created by a certain component.
const reactNodeIsOfType = (node, type) =>
typeof node === "object" && node !== null && node.type === type;
Usage:
const MyComponent = (props) => {
let hasInnerComponent = false;
React.Children.forEach(props.children, (child) => {
if (reactNodeIsOfType(child, SomeInnerComponent)) {
hasInnerComponent = true;
}
});
return (
<>
<div>hasInnerComponent: {hasInnerComponent ? "yes" : "no"}.</div>
<Wrapper>{props.children}</Wrapper>
</>
);
}
<MyComponent><div /></MyComponent>
// hasInnerComponent: no
<MyComponent><SomeInnerComponent /></MyComponent>
// hasInnerComponent: yes
const c = <SomeInnerComponent />;
console.log(reactNodeIsOfType(c, SomeInnerComponent));
// true
This is the TypeScript version. Calling it will also assign the correct type to the node's props.
const isObject = <T extends object>(value: unknown): value is T =>
typeof value === "object" && value !== null;
const reactNodeIsOfType = <
P extends object,
T extends { (props: P): React.ReactElement | null | undefined }
>(
node: React.ReactNode,
type: T
): node is { key: React.Key | null; type: T; props: Parameters<T>[0] } =>
isObject<React.ReactElement>(node) && node.type === type;

Component illogically doesn't re-render on props change

I have a weird problem with React-Redux app. One of my components doesn't re-render on props change which is updated by action 'SET_WINNING_LETTERS'. github repo: https://github.com/samandera/hanged-man
setWord.js
const initialWordState = {
word: []
};
const setWinningLetters = (wordProps) => {
let {word, pressedKey} = wordProps;
for (let i = 0; i < word.length; i++) {
if (/^[a-zA-Z]$/.test(pressedKey) && word[i].letter.toUpperCase() == pressedKey) {
word[i].visible = true;
}
}
return {word};
}
const setWord = (state = initialWordState, action) => {
switch(action.type) {
case 'SET_WORD': return Object.assign({}, state, getWord(action.word));
case 'SET_WINNING_LETTERS': return Object.assign({}, state,
updateWord(action.wordProps));
}
return state;
}
export default setWord;
In Index.js in this function the actions are triggered
handleKeyPress(pressedKey) {
store.dispatch({
lettersProps: {
word:this.props.word,
pressedKey,
missedLetters: this.props.missedLetters
},
type: 'SET_MISSED_LETTERS'
});
store.dispatch ({
wordProps: {
word:this.props.word,
pressedKey
},
type: 'SET_WINNING_LETTERS'
});
this.showEndGame(this.props.word,this.props.missedLetters);
};
componentWillMount() {
fetchWord(this.statics.maxWordLength);
window.onkeydown = () =>
{this.handleKeyPress(String.fromCharCode(event.keyCode))};
}
And in PrimaryContent.js Winning and Missing Characetrs are rendered
import React from 'react';
import {connect} from 'react-redux';
import store from '../reducers/store';
import Hangedman from './Hangedman';
import AspectRatio from './AspectRatio';
import Puzzle from './Puzzle';
import MissedCharacters from './MissedCharacters';
const mapStateToProps = (store) => {
return {
word: store.wordState.word,
missedLetters: store.missedLettersState.missedLetters
}
}
class PrimaryContent extends React.Component {
constructor() {
super();
}
renderDisabledPuzzles(amount){
return Array.from({length: amount}, (value, key) => <AspectRatio parentClass="disabled" />)
}
renderLetters(word) {
return word.map(function(letterObj, index) {
let space = (letterObj.letter==' ' ? "disabled": '')
return(
<AspectRatio parentClass={space} key={"letter" + index}>
<div id={"letter" + index}>{letterObj.visible ? letterObj.letter : ''}</div>
</AspectRatio>
)
}) ;
}
render() {
let disabledCount = this.props.puzzles - this.props.word.length;
let disabledPuzzles = this.renderDisabledPuzzles(disabledCount);
let WinningLetters = this.renderLetters(this.props.word);
return (
<div className="ratio-content primary-content">
<Hangedman/>
<MissedCharacters missedLetters={this.props.missedLetters}/>
<Puzzle>
{disabledPuzzles}
{WinningLetters}
</Puzzle>
</div>
);
}
}
export default connect(mapStateToProps)(PrimaryContent);
MissedCharacters works well while {WinningLetters} doesn't.
The action 'SET_MISSED_LETTERS' works perfect, while 'SET_WINNING_LETTERS' works only when 'SET_MISSED_LETTERS' gets updated. It means when I press one or more letter that wins they won't display until I press the letter that is missing. When I press the missing letter the component that is parent for both missing and winning letters re-renders. I was trying to pass props to PrimaryContent from it's parent but I get the same. I tried to separate {WinningLetters} in it's own component wit access to redux store but it works even worse and stops updating even when MissedCharacters updates. Can you detect where I've made a mistake?

Resources