How to pass a function to child component as a prop? - reactjs

I want to pass down a function isLoggedIn to child component HandleArticle as a prop.
Even though I am using arrow function due to this issue, TypeError: this.isLoggedIn is not a function occurs.
class HandleNews extends React.Component {
isLoggedIn = () => {
if (!this.props.isSignedIn) {
history.push("/");
} else {
return <div>hello</div>;
}
};
render() {
return (
<div>
<Link to="/news/article">
<HandleArticle isLoggedIn={this.isLoggedIn} />
</Link>
</div>
);
}
}
const mapStateToProps = state => {
console.log(state);
return {
isSignedIn: state.auth.isSignedIn
};
};
export default connect(mapStateToProps)(HandleNews);

As per the documentation, the arrow syntax is still in proposal stage for passing functions.
isLoggedIn = () => {
if (!this.props.isSignedIn) {
history.push("/");
} else {
return <div>hello</div>;
}
};
Use bind syntax in constructor for isLoggedIn function
this.isLoggedIn = this.isLoggedIn .bind(this);
Bind directly in render function
<HandleArticle isLoggedIn={this.isLoggedIn.bind(this)} />

Related

Redirect with "componentDidMount()" : problem (React)

I have some code that works. It immediately reroutes a user from the /test page to the FinishedPaying page. It is as so:
class Test extends Component {
renderRedirect = () => {
return <Redirect to="/FinishedPaying" />;
};
componentDidMount() {
this.renderRedirect();
}
...
The following code is meant to send a Paypal transaction, then route the user to the /FinishedPaying page. All of the other logic is working as expected:
export default class Pay extends React.Component {
state = {
userInput: ""
};
renderRedirect = () => {
return (
<Redirect
to="/FinishedPaying"
userInput={this.state.userInput}
/>
);
};
componentDidMount() {
this.setState({ userInput: this.props.userInput });
this.renderRedirect();
}
render() {
const onSuccess = payment => {
axios
.post(
"http://amazonaws.com:3000/ethhash",
{
userInput: this.props.userInput,
}
)
.then(response => console.log(response.data, payment))
.catch(function(error) {
console.log(error);
});
};
return (
<div>
<PaypalExpressBtn
onSuccess={onSuccess}
/>
</div>
);
}
}
Not sure why the second code block is working. It is my understanding that this.renderRedirect() should fire after all of the other logic has happened. It does not seem to be firing at all. Any feedback is appreciated :)
you can put it in your render like:
render() {
if (this.state.redirect){
return <Redirect
to="/FinishedPaying"
userInput={this.state.userInput}
/>;
}
const onSuccess = payment => {...}
As soon as you change your redirect value in state for true you will be redirected.
You can't return the component <Redirect to="/FinishedPaying" /> in componentDidMount, you can only do that in render().
You could have a flag that sets to true when you're ready to redirect:
componentDidMount() {
this.setState({
userInput: this.props.userInput,
readyToRedirect: true
});
}
Then in your render method:
render() {
this.state.readyToRedirect
? <Redirect to="/FinishedPaying" />
: other stuffs...
or in my opinion, a more readable way:
render() {
if (this.state.readyToRedirect) return <Redirect to="/FinishedPaying" />
return (
// rest of the code
)
I also wouldn't define onSuccess function inside render, every state change will trigger render and re-define the function again and again.
If it doesn't require anything from this, you can even put it outside of the class
const onSuccess = payment => {
...
}
export default class Pay extends React.Component {
...
}
export default class Pay extends React.Component {
state = {
redirect: false
};
renderRedirect = () => {
if(this.state.redirect){
return (
<Redirect
to="/FinishedPaying"
userInput={this.props.userInput}
/>
);
}
};
componentDidMount() {
this.setState({ redirect: true });
}
render() {
const onSuccess = payment => {
axios
.post(
"http://amazonaws.com:3000/ethhash",
{
userInput: this.props.userInput,
}
)
.then(response => console.log(response.data, payment))
.catch(function(error) {
console.log(error);
});
};
return (
<div>
{this.renderRedirect()}
<PaypalExpressBtn
onSuccess={onSuccess}
/>
</div>
);
}
}

How to update the state of new context provider after ajax success

New to React, trying to update the state which is initialized inside the new react context provider, after the API call is success. I am using React 16.3 .
Not able to update the state value, followed documented steps but still failed to achieve.
This is what I tried:
HTML:
<MyProvider>
<MyConsumer>
{context => (
{context.updateInitialData(this.props)}
)}
</MyConsumer>
</MyProvider>
js:
import React, { Component } from 'react';
const MyContext = React.createContext();
export const MyConsumer = HeaderContext.Consumer;
export class MyProvider extends Component {
state = {
data: null,
updateInitialData: this.updateInitialData
};
updateInitialData = () => {
this.setState({data: this.state.data})
}
render() {
return (
<MyContext.Provider
value={{
state: this.state,
updateInitialData: this.updateInitialData
}}
>
{this.props.children}
</MyContext.Provider>
);
}
}
The problem is that now even if you, correctly set the state using updateInitialData, you are actually calling the function in render which will then call setState triggering a re-render and continuing the cycle. What you need is instead to write the HOC and update the initialData in lifecycle method
import React, { Component } from 'react';
const MyContext = React.createContext();
export const MyConsumer = MyContext.Consumer;
export class MyProvider extends Component {
// you don't need to store handler in state since you are explicitly passing it as a context value
state = {
data: null
};
updateInitialData = (data) => { // getting data from passed value
this.setState({data: data})
}
render() {
return (
<MyContext.Provider
value={{
state: this.state,
updateInitialData: this.updateInitialData
}}
>
{this.props.children}
</MyContext.Provider>
);
}
}
HOC:
const withContext = (Component) => {
return class App extends React.Component {
render() {
return (
<MyConsumer>
{context => (<Component {...this.props} context={context} />)}
</MyConsumer>
)
}
}
}
and then you would use it like
class Consumer extends React.Component {
componenDidMount() {
this.props.context.updateInitialData(this.props.data);
}
render() {
}
}
export default withContext(Consumer);
and thne
<MyProvider>
<Consumer data={this.props}/>
</MyProvider>
I'm not sure whether you copy-pasted it wrong but you don't update your state with data provided to the handler:
updateInitialData = () => {
this.setState({data: this.state.data}) // ??? its doing nothing
}
try:
updateInitialData = (data) => {
this.setState({ data })
}

calling redux connect on a decorator?

I am trying to call connect on a decorator that returns a react class
const SetLanguageFromPage = () => {
return WrappedComponent =>
class setLang extends React.Component {
static propTypes = {
pathContext: PropTypes.shape({
language: PropTypes.string.isRequired
})
};
componentDidMount() {
const currentLanguage = i18n.language;
const pageLanguage = this.props.pathContext.language;
// First request
if (!currentLanguage) {
i18n.language = pageLanguage;
}
// Only update on language change
if (currentLanguage !== pageLanguage) {
i18n.changeLanguage(pageLanguage);
}
}
render() {
return <WrappedComponent {...this.props} />;
}
};
};
const mapStateToProps = (state) => { return{...} }
const mapDispatchToProps = (dis) => { return{...} }
export default connect(...)(SetLanguageFromPage);
but when I then use the decorator on another react class I get this error...
Uncaught TypeError: Cannot call a class as a function
which I suppose is from connect changing my function to a react class. Is there any way to accomplish what I am trying to do? I would really like to be able to call actions to set the state from within this decorator, but I can't see how I can get at the store to call dispatch or map the dispatch to the props...
I am using https://www.gatsbyjs.org/ for this, so the general method has the store instantiated in a way where I cannot access is directly
You get an error, because you are trying to pass and HOC to connect, whereas it expects a React component. You can instead connect the returned component inside the HOC, which is what you essentially want to do
const SetLanguageFromPage = () => {
return WrappedComponent => {
class SetLang extends React.Component {
static propTypes = {
pathContext: PropTypes.shape({
language: PropTypes.string.isRequired
})
};
componentDidMount() {
const currentLanguage = i18n.language;
const pageLanguage = this.props.pathContext.language;
// First request
if (!currentLanguage) {
i18n.language = pageLanguage;
}
// Only update on language change
if (currentLanguage !== pageLanguage) {
i18n.changeLanguage(pageLanguage);
}
}
render() {
return <WrappedComponent {...this.props} />;
}
};
return connect(mapStateToProps, mapDispatchToProps)(SetLang);
}
};
const mapStateToProps = (state) => { return{...} }
const mapDispatchToProps = (dis) => { return{...} }
export default SetLanguageFromPage;

React / Redux Async not waiting for returned response

First of all my code is working (everything is exported correctly etc ) but it's not waiting for the async return of data.
I'm using redux-thunk for my async middleware
I have an action named async.js
export function itemsHasErrored(bool) {
return {
type: 'ITEMS_HAS_ERRORED',
hasErrored: bool
};
}
export function itemsIsLoading(bool) {
return {
type: 'ITEMS_IS_LOADING',
isLoading: bool
};
}
export function itemsFetchDataSuccess(items) {
return {
type: 'ITEMS_FETCH_DATA_SUCCESS',
items
};
}
export function itemsFetchData(url) {
return (dispatch) => {
dispatch(itemsIsLoading(true));
fetch(url)
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
dispatch(itemsIsLoading(false));
return response;
})
.then((response) => response.json())
.then((items) => dispatch(itemsFetchDataSuccess(items)))
.catch(() => dispatch(itemsHasErrored(true)));
};
}
My reducer
export function itemsHasErrored(state = false, action) {
switch (action.type) {
case 'ITEMS_HAS_ERRORED':
return action.hasErrored;
default:
return state;
}
}
export function itemsIsLoading(state = false, action) {
switch (action.type) {
case 'ITEMS_IS_LOADING':
return action.isLoading;
default:
return state;
}
}
export function items(state = [], action) {
switch (action.type) {
case 'ITEMS_FETCH_DATA_SUCCESS':
return action.items;
default:
return state;
}
}
I have a container component, asyncContainer.js
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux'
import {itemsFetchData} from '../../../actions/async';
import AsyncUI from './asyncUI'
class AsyncContainer extends Component {
componentDidMount() {
this.props.fetchData('http://5826ed963900d612000138bd.mockapi.io/items');
}
render() {
if (this.props.hasErrored) {
return <p>Sorry! There was an error loading the items</p>;
}
if (this.props.isLoading) {
return <p>Loading…</p>;
}
//This fails to wait
return (
<AsyncUI />
);
}
}
AsyncContainer.propTypes = {
fetchData: PropTypes.func.isRequired,
items: PropTypes.array.isRequired,
hasErrored: PropTypes.bool.isRequired,
isLoading: PropTypes.bool.isRequired
};
const mapStateToProps = (state) => {
return {
items: state.items,
hasErrored: state.itemsHasErrored,
isLoading: state.itemsIsLoading
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchData: (url) => dispatch(itemsFetchData(url))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(AsyncContainer);
And finally I have a simple UI component named asyncUI.js written in a functional way
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux'
const AsyncUI = (items) => {
return (
<ul>
{items.map((item) => (
<li key={item.id}>
{item.label}
</li>
))}
</ul>
);
}
const mapStateToProps = (state) => {
return {
items: state.items
};
};
export default connect(mapStateToProps)(AsyncUI);
In asyncContainer.js you can see the call to my simple UI component
return (
<AsyncUI />
);
But on calling the property of the redux store items in asyncUI.js an empty array, therefore the items.map fails
However, if I remove the code from asyncUI.js and place it in asyncContainer.js it works
This is the code that works in asyncContainer.js
class AsyncContainer extends Component {
componentDidMount() {
this.props.fetchData('http://5826ed963900d612000138bd.mockapi.io/items');
}
render() {
if (this.props.hasErrored) {
return <p>Sorry! There was an error loading the items</p>;
}
if (this.props.isLoading) {
return <p>Loading…</p>;
}
//THIS IS WHERE I HAD <Async />
return (
<ul>
{this.props.items.map((item) => (
<li key={item.id}>
{item.label}
</li>
))}
</ul>
);
}
}
AsyncContainer.propTypes = {
fetchData: PropTypes.func.isRequired,
items: PropTypes.array.isRequired,
hasErrored: PropTypes.bool.isRequired,
isLoading: PropTypes.bool.isRequired
};
const mapStateToProps = (state) => {
return {
items: state.items,
hasErrored: state.itemsHasErrored,
isLoading: state.itemsIsLoading
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchData: (url) => dispatch(itemsFetchData(url))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(AsyncContainer);
I think the problem is that the component is rendering before the items data is ready. This is normal react behavior. So how do I "hold off" the rendering. As you can see I'm trying to use a Container /Component style of architecture. I can always use the solution that works as mentioned above, but I'd like to stick with this Container /Component.
Am I going to have to delve deeper into Promises etc ?
Should I not use the functional way of writing for asyncUI.js ?
I'm a little confused.
Try:
const AsyncUI = ({items}) => {
/* ^ see ^ */
return (
<ul>
{items.map((item) => (
<li key={item.id}>
{item.label}
</li>
))}
</ul>
); }
This pulls the items value off the props you reacted in the mapStateToProps function, which is an object, not an array (hence no map function).
NOTE: This should fix your issue, but it is still technically trying to render the items before they are ready in 2 instances:
The first time the component renders. The initial state for itemsIsLoading is false, so the first render will fail all the safety checks. The default value for items is [] so it should just render <ul></ul> for a very brief moment until the itemsIsLoading(true) action is dispatched. You can set the initial state to true for stop this, or change the loading check to be
if (this.props.isLoading || this.props.items.length != 0) {
return <p>Loading…</p>;
}
An argument can be made for how necessary either of those solutions actually are.
After the fetch returns the order the actions is dispatched in will result in another brief render of <ul></ul> as the loading state is set to false before the items are set. See dgrijuela's answer for one way to fix this. Another way would be to not dispatch seperate actions and have the ITEMS_FETCH_DATA_SUCCESS and ITEMS_HAS_ERRORED actions also change the itemsIsLoading value back to false (multiple reducers can act on the same action type).
You call dispatch(itemsIsLoading(false)) before dispatch(itemsFetchDataSuccess(items))
Try like this:
// async.js
...
export function itemsFetchData(url) {
return (dispatch) => {
dispatch(itemsIsLoading(true));
fetch(url)
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
})
.then((response) => response.json())
.then((items) => {
dispatch(itemsFetchDataSuccess(items));
dispatch(itemsIsLoading(false));
})
.catch(() => dispatch(itemsHasErrored(true)));
};
}
see Michael Peyper for a good answer
It turns out that the problem was with the functional style of coding of my asyncUI component. I converted it back to the 'standard' stateful component and bingo it worked.
asyncContainer.js
class AsyncContainer extends Component {
componentDidMount() {
this.props.fetchData('http://5826ed963900d612000138bd.mockapi.io/items');
}
render() {
if (this.props.hasErrored) {
return <p>Sorry! There was an error loading the items</p>;
}
if (this.props.isLoading) {
return <p>Loading…</p>;
}
return (
<AsyncUI />
)
}
}
asyncUI.js
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux'
class AsyncUI extends Component {
render() {
return (
<ul>
{this.props.items.map((item) => (
<li key={item.id}>
{item.label}
</li>
))}
</ul>
)
}
}
const mapStateToProps = (state) => {
return {
items: state.items,
};
};
export default connect(mapStateToProps)(AsyncUI);
Hope this helps anyone :-)

componentDidMount did not fire the render()

I am calling an async function in componentDidMount(), I expect after the state got updated with fetched data, the component should re-render, but no.
component code:
function mapStateToProps(state){
return {
posts: state.posts
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators(actionCreators, dispatch)
}
export default class Main extends React.Component{
constructor(props){
super(props)
}
componentDidMount(){
this.fetchData()
}
fetchData(){
this.props.getAllPosts().then(() => {
console.log('props: ' + JSON.stringify(this.props))
this.props.posts.data.map( post => {
console.log(post.content)
})
})
}
render(){
return(
<div>
{!this.props.loaded
? <h1>loading...</h1>
:
<div>
{this.props.posts.data.map(post => {
return(
<div>
<h2>{post.title}</h2>
<p>{post.content}</p>
</div>
)
})}
</div>
}
</div>
)
}
}
const Home = connect(mapStateToProps, mapDispatchToProps)(Main)
action:
export function fetchAllPosts(){
return{
type: 'FETCH_ALL_POSTS'
}
}
export function receivedAllPosts(posts){
return{
type: 'RECEIVED_ALL_POSTS',
post_list: posts
}
}
export function getAllPosts(){
return (dispatch) => {
dispatch(fetchAllPosts())
return fetch('/api/posts')
.then(response => response.json())
.then(json => {
dispatch(receivedAllPosts(json.data))
})
.catch(error => {
})
}
}
reducer:
export function posts(state = {loaded: false}, action){
switch(action.type){
case 'FETCH_ALL_POSTS':
return Object.assign({}, state, {
'loaded': false
})
case 'RECEIVED_ALL_POSTS':
return Object.assign({}, state, {
'data': action.post_list,
'loaded': true
})
default:
return state
}
}
in the console.log() in the componentDidMount(), I do see the data got fetched, so it means it is in the state, but not applied into the render(), i don't know why.
It is because of a simple reason: you should use this.props.posts.loaded, instead of this.props.loaded.
When you set your state to props:
function mapStateToProps(state){
return {
posts: state.posts
}
}
Here state.posts is actually the object from your reducer:
{
'data': action.post_list,
'loaded': true
}
So similar to use access your posts list via this.props.posts.data, you should use this.props.posts.loaded. I believe you can debug through debugger or console.log easily.
A live code: JSFiddle
If you're using multiple reducers with a root reducer, then you should also provide your reducer's name to the mapStateToProps function.
e.g:
rootreducer.js:
import posts from './posts';
const rootReducer = combineReducers({ posts });
component:
function mapStateToProps(state){
return {
posts: state.posts.posts
}
}

Resources