React not passing props to children? - reactjs

I'm trying to pass the data from this axios call into a child component, Hero. Despite having passed down the props and made a successful axios call it won't actually make it into the Hero div.
When I console.log on the child component it claims to have the data but then fails to push it to the champions array so I can't use it. Any ideas?
Edit:
I'll add in here that I do have react-router installed in this project however this data is being passed around across one "view" and not multiple pages.
This is the parent component
import React, { Component } from 'react';
import axios from 'axios';
import './assets/stylesheets/screen.css';
import Hero from './Hero';
import Info from './Info';
class Home extends Component {
constructor(props){
super(props);
this.state = { champions: [] };
}
componentDidMount() {
axios.get(
'https://api.pinterest.com/v1/boards/gasulliv/pose-
references/pins/?access_token=AQjW6hDdAF0egwEesZA6oJbqP0XQFQ-
m6_jg2RpErKPqdSA7cQAAAAA&limit=100&fields=id%2Clink%2Cnote%2
Curl%2Coriginal_link%2Cimage').then(champions => {
this.setState({ champions });
console.log(champions);
});
}
render() {
return (
<div>
<Hero champions = {this.state.champions} />
<Info />
</div>
);
}
}
export default Home;
And this is child component (at this console log I get two answers, one claiming it has the data and another claiming it does not):
import React from 'react';
import Header from './Header';
import 'bootstrap/dist/css/bootstrap.min.css';
import './assets/stylesheets/screen.css';
const Hero = (props) => {
console.log(props);
return (
<div className = "jumbotron kindred">
<Header />
<div className = "textHolder">{ props.champions.length }</div>
</div>
)
}
export default Hero;

You have to access the data in the data key response.data
Try the following.
axios.get('https://api.pinterest.com/v1/boards/gasulliv/pose-references/pins/?access_token=AQjW6hDdAF0egwEesZA6oJbqP0XQFQ-m6_jg2RpErKPqdSA7cQAAAAA&limit=100&fields=id%2Clink%2Cnote%2Curl%2Coriginal_link%2Cimage')
.then((response) => {
this.setState({
champions: response.data
})
})
.catch((error) => {
// Do something with the error
})

Thanks for help but it turns out the issue had to do with the fact that I had the router installed. Likely I just need to pass that data around through the router instead of the pages.
Kudos for the help!

Related

Using one Axios object for the whole application using Context

I have an application with several levels of children. In this example I have declared a Context.js and a ContextDBConnector.js , they are being used in this way:
Context.js
import React from 'react';
export const ContextDB = React.createContext();
ContextDBConnector.js
import React, {Component} from 'react'
import {ContextDB} from './Context'
class ContextDBConnector extends Component {
render(){
return (
<ContextDB.Provider value = {{
callAPI() {
const axios = require('axios').default
axios.get('http://localhost:5000/person_by_first_name', {
params: {
firstname: "Marcus"
}
})
.then(function (response) {
console.log("Printing out response")
console.log(response);
})
.catch(function (error) {
console.log(error);
})
.then(function () {
// always executed
})
}
}}>
{this.props.children}
</ContextDB.Provider>
)
}
}
export default ContextDBConnector;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import * as serviceWorker from './serviceWorker';
import App from './components/App.js';
import {navData} from './js/const';
import {footerData} from './js/const';
import ContextDBConnector from './context/ContextDBConnector'
ReactDOM.render(
<React.StrictMode>
<ContextDBConnector>
<App key = "0" navData = {navData} footData = {footerData} />
</ContextDBConnector>
</React.StrictMode>,
document.getElementById('root')
);
serviceWorker.unregister();
So far so good. I can now use the context to call context.callAPI() from inside a component far away down the line of children, in this way:
Image.js
import React, {Component} from 'react'
import {ContextDB} from '../context/Context'
class Image extends Component {
static contextType = ContextDB
componentDidMount(){
this.context.callAPI()
}
render(){
const obj = this.props.obj
return (
<div className={obj.classCont}>
<img className={obj.class} src={obj.url} alt='' />
</div>
);
}
}
export default Image;
This works well and looking at the javascript console in Chrome, ContextDBConnector.js is printing out what it should:
Printing out response
ContextDBConnector.js:23 {data: Array(1), status: 200, statusText: "OK", headers: {…}, config: {…}, …}
Now I'm having a hard time getting the response object from component ContextDbConnector to component Image so that it can use the data it needs. I tried something like this and it obviously didn't work:
Image.js
import React, {Component} from 'react'
import {ContextDB} from '../context/Context'
class Image extends Component {
static contextType = ContextDB
componentDidMount(){
this.context.callAPI().then(response => {
console.log("Person has been returned")
});
}
render(){
const obj = this.props.obj
return (
<div className={obj.classCont}>
<img className={obj.class} src={obj.url} alt='' />
</div>
);
}
}
export default Image;
Could you help me figure out the part where Image gets the data it needs from ContextDBConnector ?
Full disclosure: I only started learning React, Express, Mysql, Axios and Node.js like a month ago, so I apologize in advance if I'm misunderstanding some concepts here and my code logic is not very strong :D This is my first try with Context and I'm a bit stuck with this part.
Just so it's clear, what I'm trying to accomplish here is to have only one axios object for the whole app, so that the different parts can access it and get to the different routers in the Express server (backend) instead of creating axios instances all over the place.
If the way I'm trying to accomplish this is not a good one, could you show me a better way?
Otherwise, could you help me get the data from ContextDBConnector to Image in a way that Image waits for the actual object to be received before doing anything else?
Thanks and much appreciated!
as it seems you should have changed the callAPI 'cause it does not return a Promise Object so you could use .then() and .catch()

Why the data not displayed in nextjs?

I am making a very very simple nextjs application where I am trying to fetch the data from api.
My requirement is I should display the data in layout.js file and this layout.js file is a children in index.js file.
index.js:
import Layout from "./layout";
import React from "react";
class Home extends React.Component {
render() {
return (
<div>
<Layout />
<h4> Main content will be displayed here !! </h4>
</div>
);
}
}
export default Home;
layout.js:
import React from "react";
import fetch from "isomorphic-unfetch";
function Layout(props) {
return (
<div>
<p>Preact has {props.stars} ⭐</p>
<p> Why I couldn't get the above "props.star" ? </p>
</div>
);
}
Layout.getInitialProps = async () => {
console.log("comes into layout getinitial props");
const res = await fetch("https://api.github.com/repos/developit/preact");
const json = await res.json(); // better use it inside try .. catch
return { stars: json.stargazers_count };
};
export default Layout;
So as per the above given code, I have called the layout page inside index.js page (in my real application I need to call like this only so no changes in calling layout inside index)..
But when I made a console.log() in the function Layout.getInitialProps in layout, it doesn't print anything and hence the api data not fetched..
Complete working demo here with code
Why can't I fetch the data inside the layout.js while calling as a children from index.js?
Also provide me the right updated solution to achieve this.. I really searched for many questions but none solved my issue and I couldn't understand those solutions clearly so please help me with the above given example.
That because getInitialProps can only be added to the default component exported by a page, adding it to any other component won't work.
You should use componentDidMount() or useEffect instead, or move getInitialProps in the index and then pass the result to the component. something like (not tested) :
index.js :
import Layout from "./layout";
import React from "react";
class Home extends React.Component {
render() {
return (
<div>
<Layout />
<h4> Main content will be displayed here !! </h4>
</div>
);
}
}
export default Home;
layout.js
import React from "react";
import fetch from "isomorphic-unfetch";
class Layout extends React.Component {
constructor(props) {
super(props);
this.state = {
stars: false
};
}
async componentDidMount() {
console.log("comes into layout getinitial props");
const res = await fetch("https://api.github.com/repos/developit/preact");
const json = await res.json(); // better use it inside try .. catch
this.setState({ stars: json.stargazers_count });
}
render() {
const { stars } = this.state;
return (
<div>
<p>Preact has {stars} ⭐</p>
<p> Why I couldn't get the above "props.star" ? </p>
</div>
);
}
}
export default Layout;
Edit:
Example with class component
Bonus: If you want to add the layout for all the pages of your app this isn't the best approach, instead you should take a look to custom _app.js, example

Stripe - how do I save card element in react?

I'm trying to save card details for use later.
I have generated the SetupIntent client secret
I'm trying to use confirm card setup.
I'm following the docs here for react.
The following line:
const cardElement = this.props.elements.getElement('card')
is throwing me this error:
TypeError: Cannot read property 'getElement' of undefined
Where am I going wrong? My code is below:
This is the relevant portion of the main component:
import React from "react";
import { Elements, StripeProvider } from "react-stripe-elements";
import SaveCardForm from "./SaveCardForm";
<StripeProvider
apiKey={process.env.REACT_APP_API_STRIPE_PUBLISH}
>
<Elements>
<SaveCardForm/>
</Elements>
</StripeProvider>
And this is the SaveCardForm component
import React, { Component } from "react";
import { Stripe, CardElement, injectStripe } from "react-stripe-elements";
import axios from "axios";
class SaveCardForm extends Component {
constructor(props) {
super(props);
this.submit = this.submit.bind(this);
}
submit = e => {
e.preventDefault()
const cardElement = this.props.elements.getElement('card');
axios.get(`${process.env.REACT_APP_API}/saveCardDetails`).then(res => {
console.log('res.data', res.data)
this.props.stripe.confirmCardSetup(res.data.client_secret, {
payment_method: {
card: cardElement,
},
}).then( confirmCardSetupRes => {
console.log('confirmCardSetupRes', confirmCardSetupRes)
})
})
}
render() {
return (
<div>
<CardElement />
<button onClick={this.submit}>
Bid For Tickets
</button>
</div>
);
}
}
export default injectStripe(SaveCardForm);
Given your components, there is no prop named elements passed into SaveCardForm. If it's access to CardElement you are after, use a ref which will give you a direct reference to that component e.g.
constructor(props) {
...
this.cardEl = React.createRef();
}
submit = e => {
...
const card = this.cardEl.current.<accessDomHere>;
this.props.stripe.confirmCardSetup(res.data.client_secret, {
payment_method: {
card
},
}).then(...)
}
render() {
...
<div>
<CardElement ref={this.cardEl} />
...
</div>
}
Switch out <accessDomHere> for whatever DOM query you need to perform to get the information you need. There may even be a React property or function you can access (I'm not familiar with the component).
I resolved this by updating to the latest version of react-stripe-elements.
There is an error in the versions before 5.1.0

How to include the Match object into a ReactJs component class?

I am trying to use my url as a parameter by passing the Match object into my react component class. However it is not working! What am I doing wrong here?
When I create my component as a JavaScript function it all works fine, but when I try to create my component as a JavaScript class it doesn't work.
Perhaps I am doing something wrong? How do I pass the Match object in to my class component and then use that to set my component's state?
My code:
import React, { Component } from 'react';
import axios from 'axios';
import PropTypes from 'prop-types';
class InstructorProfile extends Component {
constructor(props, {match}) {
super(props, {match});
this.state = {
instructors: [],
instructorID : match.params.instructorID
};
}
componentDidMount(){
axios.get(`/instructors`)
.then(response => {
this.setState({
instructors: response.data
});
})
.catch(error => {
console.log('Error fetching and parsing data', error);
});
}
render(){
return (
<div className="instructor-grid">
<div className="instructor-wrapper">
hi
</div>
</div>
);
}
}
export default InstructorProfile;
React-Router's Route component passes the match object to the component it wraps by default, via props. Try replacing your constructor method with the following:
constructor(props) {
super(props);
this.state = {
instructors: [],
instructorID : props.match.params.instructorID
};
}
Hope this helps.
Your constructor only receives the props object, you have to put match in it...
constructor(props) {
super(props);
let match = props.match;//← here
this.state = {
instructors: [],
instructorID : match.params.instructorID
};
}
you then have to pass that match object via props int a parent component :
// in parent component...
render(){
let match = ...;//however you get your match object upper in the hierarchy
return <InstructorProfile match={match} /*and any other thing you need to pass it*/ />;
}
for me this was not wrapping the component:
export default (withRouter(InstructorProfile))
you need to import withRouter:
import { withRouter } from 'react-router';
and then you can access match params via props:
someFunc = () => {
const { match, someOtherFunc } = this.props;
const { params } = match;
someOtherFunc(params.paramName1, params.paramName2);
};
Using match inside a component class
As stated in the react router documentation. Use this.props.match in a component class. Use ({match}) in a regular function.
Use Case:
import React, {Component} from 'react';
import {Link, Route} from 'react-router-dom';
import DogsComponent from "./DogsComponent";
export default class Pets extends Component{
render(){
return (
<div>
<Link to={this.props.match.url+"/dogs"}>Dogs</Link>
<Route path={this.props.match.path+"/dogs"} component={DogsComponent} />
</div>
)
}
}
or using render
<Route path={this.props.match.path+"/dogs"} render={()=>{
<p>You just clicked dog</p>
}} />
It just worked for me after days of research. Hope this helps.
In a functional component match gets passed in as part of props like so:
export default function MyFunc(props) {
//some code for your component here...
}
In a class component it's already passed in; you just need to refer to it like this:
`export default class YourClass extends Component {
render() {
const {match} = this.props;
console.log(match);
///other component code
}
}`

React + Redux: Separating the presentation from the data

I am building a weather app with React & Redux. I've decided to venture into uncharted waters as a noob to React & Redux. I'm splitting things up into presentational components and their respective container that will handle the data. I'm having some problems wrapping my head around this though. It might come down to how I'm trying to do it I'm just really unsure.
Right now I have SearchBar, CurrentWeather, & Forecast components and an AppContainer that I'm trying to integrate those components into. I have the SearchBar component integrated into the AppContainer so far and it is working with no problems. Here is where I am getting confused. So I have provided the needed actions and components to the container and the container has been connected so when the user does a search the api call will be made and the state will update through the reducers.
That data should be available through mapStateToProps now correct?
How can I go about using that data after the user has performed the action but have it not be used upon the initial render? If AppContainer is rendering these three components I will obviously be passing props to them so they render and function as they are expected to. I'm thinking this is where a lifecycle could be used I'm just unsure of which or how to use them. My code for the AppContainer, SearcBar, & CurrentWeather are below. CurrentWeather & Forecast are nearly identical (only providing different data from different endpoints for the api) so I did not provide it. I also didn't provide the actions or reducers because I know they work fine before I decided to attempt this refactor. Maybe I need more than one container to pull this off? Any advice or direction would be greatly appreciated, thanks all and have a good night.
** Do have a side question: on _weatherSearch I have event.preventDefault(); because the SearchBar is a form element. Do I even need to provide this? If event is not what is being passed but the term I think no. The event is being used as seen below in the form element of SearchBar:
onSubmit={event => getWeather(event.target.value)}
App Container:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchCurrentWeather, fetchForecast } from '../actions/actions';
import SearchBar from '../components/SearchBar';
import CurrentWeather from '../components/CurrentWeather';
class AppContainer extends Component {
_weatherSearch(term) {
event.preventDefault();
// Here is where we go to fetch weather data.
this.props.fetchCurrentWeather(term);
this.props.fetchForecast(term);
}
render() {
const getWeather = term => {this._weatherSearch(term);};
return (
<div className="application">
<SearchBar getWeather={getWeather}/>
<CurrentWeather />
</div>
);
}
}
const mapStateToProps = ({ current, forecast }) => {
return {
current,
forecast
}
}
export default connect(mapStateToProps,
{ fetchCurrentWeather, fetchForecast })(AppContainer);
SearchBar:
import React from 'react';
const SearchBar = ({ getWeather }) => {
return(
<form className='input-group' onSubmit={event => getWeather(event.target.value)}>
<input
className='form-control'
placeholder='Search a US City' />
<span className='input-group-btn'>
<button className='btn btn-secondary' type='submit'>Submit</button>
</span>
</form>
);
}
export default SearchBar;
CurrentWeather: *NOTE: I have not removed any of the logic or data processing from CurrentWeather yet so it has not been refactored to a presentational only component yet.
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {unitConverter} from '../conversions/conversions_2.0';
export class CurrentWeather extends Component {
_renderCurrentWeather(cityData) {
const name = cityData.name;
const {temp, pressure, humidity} = cityData.main;
const {speed, deg} = cityData.wind;
const {sunrise, sunset} = cityData.sys;
return (
<tr key={name}>
<td>{unitConverter.toFarenheit(temp)} F</td>
<td>{unitConverter.toInchesHG(pressure)}"</td>
<td>{humidity}%</td>
<td>{unitConverter.toMPH(speed)}mph {unitConverter.toCardinal(deg)}</td>
</tr>
);
}
render() {
let currentWeatherData = [];
if (this.props.current) {
currentWeatherData = this.props.current.map(this._renderCurrentWeather);
}
return (
<table className="table table-reflow">
<thead>
<tr>
<th>Temperature</th>
<th>Pressure</th>
<th>Humidity</th>
<th>Wind</th>
</tr>
</thead>
<tbody>
{currentWeatherData}
</tbody>
</table>
);
}
}
function mapStateToProps({current}) {
return {current};
}
export default connect(mapStateToProps)(CurrentWeather);
Your render function is very dynamic. You can omit anything you like:
class AppContainer extends Component {
_weatherSearch(term) {
// event.preventDefault(); We can't do this because we don't have an event here...
this.props.fetchCurrentWeather(term);
this.props.fetchForecast(term);
}
render() {
const getWeather = term => { this._weatherSearch(term); };
return (
<div className="application">
<SearchBar getWeather={getWeather}/>
{ Boolean(this.props.current) && <CurrentWeather /> }
</div>
);
}
}
const mapStateToProps = ({ current }) => ({ current });
export default connect(mapStateToProps,
{ fetchCurrentWeather, fetchForecast })(AppContainer);
This is how you deal with missing data. You just either show nothing, or a message to search first, or if it's loading,you can show a spinner or throbber.
The technique used above to hide CurrentWeather is to pass a Boolean to React if we're wanting to hide the component. React ignores true, false, null and undefined.
Note that it's a good idea to only ever pass data in mapStateToProps that you'll actually be using inside the component itself. In your code you're passing current and forecast but you don't use them.
Redux will rerender when any of the mapStateToProps, mapDispatchToProps or props data changes. By returning data you'll never use you instruct Redux to rerender when it's not necessary.
I'm a react-redux noob myself :-) and I've come across similar issues.
As far as I can tell, the container/presentational separation you've made looks good, but you can go even a step further and separate the container's fetching and mounting.
The solution I'm referring to is what people variously call "higher-order components" and "wrapper components": (the code below isn't tested - it's just for illustration purposes)
import {connect} from blah;
const AppWrap = (Wrapped) => {
class AppWrapper extends Component {
constructor(props) {
super(props);
this.state = {foo: false};
}
componentWillMount() {
this.props.actions.fooAction()
.then(() => this.setState({foo: false}));
}
render() {
return (<Wrapped {...this.props} foo={this.state.foo}/>);
}
}
function mapState(state) { blah }
function mapDispatch(dispatch) { blah }
return connect(mapState, mapDispatch)(AppWrapper);
}
export default AppWrap;
Notice the = (Wrapped) => { part at the top. That is what's doing the actual "wrapping", and the argument can be named anything so long as you refer to it in the render hook.
Now inside your AppContainer, you get a this.props.foo which acts as a flag telling you that fooAction() has completed, and you can use it to render your presentational components accordingly. Until fooAction completes, you can be sure that the foo passed into AppContainer will be false.
To put what I just said into code, your AppContainer might look something like this:
import AppWrapper from './AppWrapper';
class AppContainer extends Component {
constructor(props) {
super(props);
}
render() {
return (!this.props.foo) ? <div>bar</div> : (
<div blah>
<SearchBar blah/>
<CurrentWeather blah/>
</div>
);
}
}
export default AppWrapper(AppContainer);
The benefit of using a wrapper component like this is that
you can take more control over how and when exactly the data gets rendered
account for "loading" mechanisms and logic
avoid quirky problems like having to make dispatches within componentWillMount hooks and having to deal with the consequences.
Take a look at this blog post for more information about HoCs: https://medium.com/#dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750

Resources