Component rendering before ComponentDidMount and Axios sets the state - javascript

Ok so in the render method I pass the gifs state to my GifList component the problem is when I try to use that array in that component thru props its saying that its undefined and upon further review I can see that the gifs property in the app's state is originally being passed as an empty array before the setState is setting it to the return value of my Axios call in the lifecycle hook because of Axios being async. How can I fix this issue??
import React, { Component } from 'react';
import axios from "axios";
import styles from './App.css';
import Header from './Components/Header/Header';
import GifList from './Components/GifList/GifList';
class App extends Component {
state = {
title: "Giphy Search App",
gifs: []
}
componentDidMount() {
axios.get("http://api.giphy.com/v1/gifs/search? q=funny+cat&limit=20&api_key=ms344CewNH5NEbybHwQifMZImoQfEQ38")
.then((res) => {
const arr = res.data.data;
this.setState({ gifs: arr });
});
}
render() {
return (
<div className={styles.app}>
<Header title={this.state.title}/>
<GifList gifList={this.state.gifs}/>
</div>
);
}
}
export default App;

You can wait to render your GifList until your gifs array has something in them. This is basically an inline if statement for jsx.
render() {
return (
<div className={styles.app}>
<Header title={this.state.title}/>
{this.state.gifs.length > 0 && <GifList gifList={this.state.gifs}/>}
</div>
);
}

you can render GifList only after the list has some items
render() {
return (
<div className={styles.app}>
<Header title={this.state.title}/>
{
this.state.gifs.length &&
<GifList gifList={this.state.gifs}/>
}
</div>
);
}
export default App;

Related

ReactJs: How to get api data in child component with props?

I am trying to call api data only once thats way I call api in home.js file with componentdidmount in class component and i want to render this data in many child components with functional components.when i call api in every each child component,its work but when i try to call with props coming only empty array by console.log please help.
import React from 'react'
import '../styles/home.css'
import axios from 'axios';
import Teaser from './Teaser'
import Second from './Second'
import Opening from './Opening'
import Menu from './Menu'
export default class Home extends React.Component {
state = {
posts: []
}
componentDidMount() {
axios.get("https://graph.instagram.com/me/media?fields=id,caption,media_url,permalink,username&access_token=IGQ")
.then(res => {
const posts = res.data.data;
this.setState({ posts });
})
}
render() {
return (
<>
<Teaser/>
<Second/>
<Opening/>
<Menu posts={this.state.posts}/>
</>
)
}
}
import React from 'react'
import axios from 'axios';
function Menu(props) {
const {posts} = props.posts;
console.log(props);
return (
<>
{posts.map(
(post) =>
post.caption.includes('#apegustosa_menu') &&
post.children.data.map((x) => (
<div className="menu_item" key={x.id}>
<img className="menu_img" src={x.media_url} alt="image" />
</div>
)),
)}
</>
)
}
export default Menu

Pass data from parent to child and sibling children using context api

I am trying to display fetched data in child component, using context api. But I'm getting below error on browser
TypeError: render is not a function
The above error occurred in the component:
in AppDataList (at App.js:32)
in div (at App.js:30)
in App (at src/index.js:7)
and below warning
Warning: A context consumer was rendered with multiple children, or a
child that isn't a function. A context consumer expects a single child
that is a function. If you did pass a function, make sure there is no
trailing or leading whitespace around it.
App.js
import React, { Component } from "react";
import "./App.css";
import AppDataList from "./components/AppDataList";
export const AppContext = React.createContext();
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
appData: []
};
}
fetchAppData() {
fetch(` http://localhost:4000/AppDataList`)
.then(res => res.json())
.then(res => {
console.log(res)
this.setState({
appData: res
});
});
}
componentDidMount() {
this.fetchAppData();
}
render() {
return (
<div className="App">
<AppContext.Provider>
<AppDataList />
</AppContext.Provider>
</div>
);
}
}
AppDataList.js
import React, { Component } from "react";
import { AppContext } from "../App";
export default class AppDataList extends Component {
render() {
return (
<AppContext.Consumer>
<div>{context => <p>{context.state}</p>}</div>
</AppContext.Consumer>
);
}
}
I also want to do something like
<AppContext.Provider>
<Child1 />
<Child2 />
<Child3 />
</AppContext.Provider>
and consume data in respective child component.
You have to put the value you want to pass to Consumers via the value prop in the Provider:
<Context.Provider value={{ appData }}>
Below works:
<AppContext.Consumer>
{context => <p>{context.state}</p>}
</AppContext.Consumer>
consumer looks for function, not component.
ref: Seeing "render is not a function" when trying to use Context API

Does returning null in a component prevents child components from rendering?

I have a LoadingProvider where I set the state of my Loading component to false and when needed to true. I want to show my Loading component only when the state of loading equals to true.
All my providers, router and app components are loaded in my root.js:
import React, { Component } from "react";
import { BrowserRouter as Router } from "react-router-dom";
import { MuiPickersUtilsProvider } from "material-ui-pickers";
import MomentUtils from "#date-io/moment";
import App from "./App";
import { DeleteDialogProvider } from "/hocs/withDeleteDialog";
import { WarningDialogProvider } from "/hocs/withWarningDialog";
import { LoadingProvider } from "/hocs/withLoading";
import { MuiThemeProvider, createMuiTheme } from "#material-ui/core/styles";
import { StateProvider } from "/hocs/withState";
import { I18nProvider } from "/hocs/withI18n";
const theme = createMuiTheme({});
class Root extends Component {
render() {
return (
<MuiThemeProvider theme={theme}>
<MuiPickersUtilsProvider utils={MomentUtils}>
<I18nProvider>
<DeleteDialogProvider>
<WarningDialogProvider>
<StateProvider>
<Router>
<LoadingProvider>
<App />
</LoadingProvider>
</Router>
</StateProvider>
</WarningDialogProvider>
</DeleteDialogProvider>
</I18nProvider>
</MuiPickersUtilsProvider>
</MuiThemeProvider>
);
}
}
export default Root;
My other providers don't block any other components from rendering. But when I add the LoadingProvider in root.js and check the console with the React Developer Tools I see it doesn't load/render the components that comes after my LoadingProvider component. The problem is that I don't know why it doesn't render any other components.
This is my withLoading file where I define the LoadingProvider:
import React, { Component } from "react";
import Loading from "/components/Loading";
const LoadingContext = React.createContext();
export class LoadingProvider extends Component {
constructor(props) {
super(props);
this.state = {
loading: false
};
}
setLoadingContext = e => {
this.setState({
loading: true
});
};
render() {
return (
<LoadingContext.Provider value={this.setLoadingContext}>
<Loading
loading={this.state.loading}
/>
</LoadingContext.Provider>
);
}
}
export const withLoading = Component => props => (
<LoadingContext.Consumer>
{setLoadingContext => (
<Component {...props} setLoadingContext={setLoadingContext} />
)}
</LoadingContext.Consumer>
)
And this is my Loading.js file where I define my Loading component:
import React, { Component } from 'react';
import CircularProgress from '#material-ui/core/CircularProgress';
class Loading extends Component {
render() {
const loading = this.props;
// TODO: fix weird repetitive loading prop
if (!loading.loading) {
return null;
} else {
return (
<CircularProgress />
);
}
}
}
export default Loading;
I guess it has something to do with returning null when loading is false. But when I comment that rule of code out it says:
Uncaught Invariant Violation: Loading(...): Nothing was returned from
render. This usually means a return statement is missing. Or, to
render nothing, return null.
This is primarily because in your LoadingProvider you are not using props.children.
<LoadingContext.Provider value={this.setLoadingContext}>
<Loading
loading={this.state.loading}
/>
{this.props.children} // add this
</LoadingContext.Provider>
Take note that null don't render anything.
Your <App/> is passed to LoadingProvider in its children property. But LoadingProvider doesn't do anything with its children, so nothing happens.
So return this.props.children when you want them to render.

this.props.match.params passed into child component after authorisation

I have recently started building a big project on React using also a Firebase with authentication and I cannot quite understand the relation between the react-router-dom links and React components.
I am struggling with getting the
this.props.match.params // which is going to be 2018 / 2019 / 2020... etc
in the component, which renders as a dynamic route (like unique post component).
I have tried to use only a simple class component and this works but the problem is, without the authentication everyone can access this admin route and everyone would be allowed to edit and delete data there. I want it to be accessed only by authenticated users. (Admins)
So this is how my piece of code looks like:
Main component: (where the link is)
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
class SeasonBox extends Component {
render() {
return (
<Link className='seasonbox' to={`/adminseason/${this.props.season}`}>
<p className='seasonbox__season'>{this.props.season}/{this.props.season+1}</p>
</Link>
)
}
}
export default SeasonBox;
And the component that renders after the link is clicked:
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
import { connect } from 'react-redux'
import { compose } from 'recompose'
import { withAuthorisation } from '../Session'
import { withFirebase } from '../Firebase'
const AdminMatchesBox = ({authUser}) => (
<div>{authUser ? <AdminMatchesBoxAuth /> : <AdminMatchesBoxNonAuth />} </div>
)
class AdminMatchesBoxAuth extends Component {
render() {
return (
<div>
Hey I am the season {this.props.match.params}!
<Link to={'/adminmatches'}>Wróć</Link>
</div>
)
}
}
const AdminMatchesBoxNonAuth = () => (
<div>
<h1>You do not have permission to visit this page.</h1>
</div>
)
const mapStateToProps = state => ({
authUser: state.sessionState.authUser
});
const condition = authUser => !!authUser
export default compose(withAuthorisation(condition), connect(mapStateToProps),withFirebase)(AdminMatchesBox);
So if I don't use authorisation, and I use only a single class component I can get this.props.match.params -> which is the id of the website and I need it to access data from the database.
However, I want it to not be visible by not logged users and I had to process it through the authorisation process.
I am receiving an error
Cannot read property 'params' of undefined.
I have no clue how to pass match.params into the AdminMatchesBoxAuth component.
Could anyone advice?
By wrapping withRouter you able to access params
Try this
import { withRouter } from "react-router";
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
import { connect } from 'react-redux'
import { compose } from 'recompose'
import { withAuthorisation } from '../Session'
import { withFirebase } from '../Firebase'
const AdminMatchesBox = ({authUser}) => (
<div>{authUser ? <AdminMatchesBoxAuth /> : <AdminMatchesBoxNonAuth />} </div>
)
class AdminMatchesBoxAuth extends Component {
constructor (props){
super(props)
}
render() {
return (
<div>
Hey I am the season {this.props.match.params}!
<Link to={'/adminmatches'}>Wróć</Link>
</div>
)
}
}
const AdminMatchesBoxNonAuth = () => (
<div>
<h1>You do not have permission to visit this page.</h1>
</div>
)
const mapStateToProps = state => ({
authUser: state.sessionState.authUser
});
const condition = authUser => !!authUser
export default compose(withRouter, withAuthorisation(condition), connect(mapStateToProps),withFirebase)(AdminMatchesBox)

How to get refs from another component in React JS

The code in main App component is as follows :
class App extends Component {
componentDidMount(){
console.log(this.ref);
debugger;
}
render() {
return (
<div>
<Header/>
{this.props.children}
<Footer/>
</div>
);
}
}
And one of the components which renders with {this.props.children} is HomePage, where are sections with refs.
The code of a HomePage is as follows :
render(){
return (
<div className="homeMain">
<section ref="info"> <Info/> </section>
<section ref="contact"> <Contact /> </section>
</div>
);
}
How can I get those refs inside App component to be able to pass them as props to header?
I'm trying to do it inside componentDidMount in App component, but console.log(this.refs) is empty.
Any advice?
EDIT
The whole App component :
import React, {Component} from 'react';
import Footer from './common/footer';
import Header from './common/header';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import * as actions from './components/homepage/login/authActions';
class App extends Component {
componentDidMount(){
console.log(this.props.children.refs);
debugger;
}
render() {
return (
<div>
<Header route={this.props.location.pathname}
language={this.props.language.labels}
authenticated={this.props.authenticated}
signoutAction={this.props.actions}
offsets={this.props.offsets}
/>
{React.cloneElement(this.props.children, {
currentLanguage: this.props.language.labels,
authenticated: this.props.authenticated
})}
<div className="clearfix"/>
<Footer currentLanguage={this.props.language.labels}/>
</div>
);
}
}
function mapStateToProps(state, ownProps) {
return {
language: state.language,
authenticated: state.auth.authenticated,
offsets: state.offsets
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
The React's main idea is passing props downward from parent to children (even to deeper levels of children - grandchildren I mean)
Therefore, when we want the parent to do something which is triggered from (or belongs to) the children, we can create a callback function in the parent, then pass it down to children as props
For your preference, this is a demonstration on how to pass callback functions downward through many levels of children and how to trigger them:
Force React container to refresh data
Re-initializing class on redirect
In your case, you can access refs from children components as follows: (using string for ref - as you stated)
Parent Component:
import React, { Component } from 'react';
// import Child component here
export default class Parent extends Component {
constructor(props){
super(props);
// ...
this.getRefsFromChild = this.getRefsFromChild.bind(this);
}
getRefsFromChild(childRefs) {
// you can get your requested value here, you can either use state/props/ or whatever you like based on your need case by case
this.setState({
myRequestedRefs: childRefs
});
console.log(this.state.myRequestedRefs); // this should have *info*, *contact* as keys
}
render() {
return (
<Child passRefUpward={this.getRefsFromChild} />
)
}
}
Child Component:
import React, { Component } from 'react';
export default class Child extends Component {
constructor(props){
super(props);
// ...
}
componentDidMount() {
// pass the requested ref here
this.props.passRefUpward(this.refs);
}
render() {
return (
<div className="homeMain">
<section ref="info"> <Info/> </section>
<section ref="contact"> <Contact /> </section>
</div>
)
}
}
ref is property of each this.props.children hence you can access ref of child component in parent via ref property on this.props.children
Make sure you access ref after componentDidMount
Edit :
Try below set of code if this works :
var myChild= React.Children.only(this.props.children);
var clone = React.cloneElement(myChild, { ref: "myRef" });

Categories