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

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

Related

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.

Next.JS -- getInitialProps to pass props and state to child components via ContextProvider

I am using getInitialProps() in _App.js to grab some api data and I want to pass that data down as props to my other components via React Context. I want to initialize the Context Provider with state via the constructor.
First I initialize context via createContext().
config/Context.js:
import { createContext } from 'react';
const Context = createContext();
export default Context;
Then I create Context.Provider in its own component using constructor to initialize state.
provider/ContextProvider.js:
import React, { Component } from 'react';
import Context from '../config/Context';
class ContextProvider extends Component {
constructor(props) {
super(props);
this.state = {
filters: {
active: true,
headerActive: false
}
};
}
render() {
return (
<Context.Provider>
{this.props.children}
</Context.Provider>
);
}
}
export default ContextProvider;
In _app.js I make an API call within getInitialProps() and pass that data into the Context Provider.
pages/_app.js
import App, { Container } from 'next/app';
import ContextProvider from '../provider/ContextProvider';
import fetch from 'isomorphic-unfetch';
export default class MyApp extends App {
static async getInitialProps() {
const res = await fetch('https://node-hnapi.herokuapp.com/news');
let data = await res.json();
console.log(data)
return { articles: data }
}
render () {
const { Component, pageProps } = this.props;
return (
<Container>
<ContextProvider value={{ articles: this.props.articles}}>
<Component {...pageProps} />
</ContextProvider>
</Container>
);
}
}
At this point I assume that I would have access to context via Context.Consumer or a hook like useContext() but context is undefined in the following component:
components/ArticleList.js:
import React from 'react';
import Context from '../config/Context';
const ArticleList = () => {
const generateArticles = () => {
const context = React.useContext(Context);
console.log(context, 'context') // Context is undefined here
// return context.articles.map(article => <p>{article.title}</p>)
// Error: Cannot read property 'articles' because context is undefined
}
return (
<div>
<h3>Article List</h3>
{generateArticles()}
</div>
);
};
export default ArticleList;
Why is context undefined in components/ArticleList.js? I tried passing context into the component via Context.Consumer and I got the same result.
Here is repo replicating the issue: https://github.com/joelhoelting/next-context-api-test
In ContextProvider.js you forget to pass value to Context.Provider
import React, { Component } from 'react';
import Context from '../config/Context';
class ContextProvider extends Component {
constructor(props) {
super(props);
this.state = {
filters: {
active: true,
headerActive: false
}
};
}
render() {
const { value } = this.props
return (
<Context.Provider value={ value }>
{this.props.children}
</Context.Provider>
);
}
}
export default ContextProvider;

React Context Decorator/Subscriber?

Edit: Perhaps this could be referenced as a context subscriber?
I'm not even sure if this is the right concept that I'm trying to achieve. I want to be able to create a component that does the dirty work and just attaches context to the component that can the be consumed..
I've tried to find anything similar with no luck, which, leads me to believe I am not thinking of the right literal context of what it is I'm doing...
I've tried something like this:
import React, { Component } from "react";
export const Context = React.createContext();
export class ContextProvider extends Component {
state = {
scanning: false
};
render() {
return (
<Context.Provider
value={{
state: this.state,
handleClick: () => this.setState({
scanning: !this.state.scanning
})
}}
>
{this.props.children}
</Context.Provider>
);
}
}
And I trying to make it work with this..
import React from "react";
import { Context } from "./Context";
const WithContext = (children) => (props) => {
return (
<Context.Consumer>
{ state => (<children {...props} context={state} />) }
</Context.Consumer>
)
};
and then consuming with...
...
<WithContext>
<MyComponent />
</WithContext>
...
But, it just seems to fail or states that I'm returning a function instead of a react component..
Your WithContext component will not work like that... It needs to be a function that has the same functionality as the render function. like so:
import React from "react";
import { Context } from "./Context";
const WithContext = ({ children, ...props }) => (
<Context.Consumer>{state => React.Children.map(children, (child) => (
React.cloneElement(child, { context: state })
))}</Context.Consumer>
);
note that we traverse every direct child of the withContext children using React.Children.map (docs) and add a context prop to them by making use of React.cloneElement (docs). This keeps the child component's original props and shallow merges them into the second parameter passed to the function.
There are a bunch of little errors in your code for using context... Here is a complete example...
Let's say we have a structure where we have App.js -> Parent.js -> Child.js components... Instead of passing the data via props from App to Parent to Child we want to make use of the context API and avoid the prop drilling and have the Child consume the data directly...
Here is what that will look like:
context.js:
import React from 'react';
const Context = React.createContext();
export class Provider extends React.Component {
state = { name: 'Bob', age: 20 };
handleGrowUp = () => {
this.setState({ age: this.state.age + 1 });
};
render() {
return (
<Context.Provider
value={{
state: {
...this.state,
},
actions: {
growUp: this.handleGrowUp,
},
}}
>
{this.props.children}
</Context.Provider>
);
}
}
export const Consumer = Context.Consumer;
App.js:
import React from 'react';
import Parent from './Parent';
import { Provider } from './context';
const App = () => (
<Provider>
<Parent />
</Provider>
);
export default App;
Parent.js:
import React from 'react';
import Child from './Child';
const Parent = () => (
<div>
<Child />
</div>
);
export default Parent;
Child.js:
import React from 'react';
import { Consumer } from './context';
const Child = () => (
<div>
<Consumer>
{value => (
<div>
<p>{value.state.name}</p>
<p>{value.state.age}</p>
<button onClick={value.actions.growUp}>Grow Up</button>
</div>
)}
</Consumer>
</div>
);
export default Child;
Here is a working demo: https://codesandbox.io/s/9z06xzlyly

How can I access props passed to React.Component

I want to get some props made in the root layer of my react app:
import React from 'react'
import App, { Container } from 'next/app'
export default class MyApp extends App {
static async getInitialProps({ Component, router, ctx }) {
let pageProps = {}
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx)
}
return { pageProps }
}
state = {
language: "pl"
};
render () {
const { Component, pageProps } = this.props
return (
<Container>
<Component lang={this.state.language} />
</Container>
)
}
}
so every new React.Component created should inherit those props. But I'm not sure how I can get them. Let's say I have another component which is <Nav/>.
Shouldn't I be able to get it via props.lang inside Nav.
When I try it says lang undefined.
I would suggest moving language to the React Context API
So this way you create a context
// context.js
import React from 'react';
export const LangContext = React.createContext('pl');
and provide it inside _app.js
// app.js
import React from 'react';
import App, { Container } from 'next/app';
import { LangContext } from '../context';
export default class MyApp extends App {
static async getInitialProps({ Component, router, ctx }) {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
return { pageProps };
}
state = {
language: 'EN'
};
render() {
const { Component, pageProps } = this.props;
return (
<Container>
<LangContext.Provider value={this.state.language}>
<Component {...pageProps} />
</LangContext.Provider>
</Container>
);
}
}
and whenever you need to access language value you dont need to pass it anymore. It will be available on LangContext. Example usage
// Nav.js
import Link from 'next/link';
import { LangContext } from '../context';
function Nav() {
return (
<LangContext.Consumer>
{lang => {
return (
<div className="site-nav">
<Link href="/">
<a>index</a>
</Link>
<Link href="/about">
<a>about</a>
</Link>
language = {lang}
</div>
);
}}
</LangContext.Consumer>
);
}
export default Nav;
This helps to solve the issue of passing lang props to pages and then to some specific components like Nav. Just wrap a component into a <LangContext.Consumer> if you need it.
Example index.js page
// index.js
import Nav from '../components/Nav';
export default () => (
<div>
<Nav />
<hr />
Welcome to index.js!
</div>
);
** One note: as far as I see you can only use <SomeContext.Provider> inside _app.js
I'm seeing a couple problems in your code example.
First, props are a property on your component, they should be accessed via this.props.
Here is a basic example of passing props to a child component:
import React, { Component } from 'react';
class App extends Component {
render() {
const greeting = 'Welcome to React';
return (
<div>
<Greeting greeting={greeting} />
</div>
);
}
}
class Greeting extends Component {
render() {
return <h1>{this.props.greeting}</h1>;
}
}
export default App;
Using the code sample above, it would seem that your mistake was to use return <h1>{props.greeting}</h1>; instead of return <h1>{this.props.greeting}</h1>;
Second, it would appear that your component setup is a little off. I would expect your component declaration to look something like this:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
In your code sample, there's no constructor function and state doesn't appear to be set as a property of your component.
Inside of the example <Nav/> component, you must specify at least one argument in the component's function if you wish to access this.props. For example:
const Nav = (props) => ( <div> {this.props.lang} </div> )
Hope this helps!
Summary of my comments above:
Did you try props.lang, or, this.props.lang?
Because you need this.props.lang to access the property.
Hrm, just took a quick peek at my own code -- the initial state is set in constructor(props), and is defined like super(); this.state = (somestate);.
Because you need to set the state in the constructor of classes.

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