function returning data but not showing - reactjs

I have this component
const SummaryBar = props => {
const { MainObject} = props;
const localGetUserFromID = userID => {
getEmailFromId(userID).then(results => {
return results.data.Title; //Comment: This one returning friendly name
});
};
return (<span>Hello {localGetUserFromID(MainObject.AuthorId)}</span>)
}
but when I render it somehow the its only showing Hello and not the output I am getting from my localGetUserFromID function. Am I doing wrong? Note the AuthorId is being pass to an API and the MainObject came from the App Level,
FYI when I try to debug it using dev tools the function is retuning the text I am look for.

localGetUserFromID() returns nothing, that is, undefined, and that's why you see Hello only.
And because localGetUserFromID() makes an asynchronous call to get an email from user ID, it doesn't have to be in render() method. Now this component is defined as a state-less component, but you can re-define it as a stateful component, call the getEmailFromId() in componentDidMount() life-cycle method, and use a return value as an internal state.
Then you can show a value of the internal state after Hello.
class SummaryBar extends Component {
// Skipping prop type definition.
constructor(props) {
super(props)
this.state = {
username: '',
}
}
componentDidMount() {
const { MainObject: { AuthorId } } = this.props
getEmailFromId(AuthorId).then((results) => {
this.setState({
username: results.data.title,
})
})
}
render() {
const { username } = this.state
return (
<span>
Hello { username }
</span>
)
}
}

When things run when debugging but not when running and you are using promises as you are, the 99% of the times is because promises hasn't been resolved when you print.
localGetUserFromID indeed returns a promise that resolves to the friendly name.
You can just prepend await to localGetUserFromID(MainObject.AuthorId) and rewrite you return as this:
return (<span>Hello {await localGetUserFromID(MainObject.AuthorId)}</span>)

Related

Render an array as list with onClick buttons

I'm new at ReactJs development, and I'm trying to render a list below the buttons I created with mapping my BE of graphQl query. I don't know what I'm doing wrong (the code has a lot of testing on it that I tried to solve the issue, but no success.)
The buttons rendered at getCategories() need to do the render below them using their ID as filter, which I use another function to filter buildFilteredCategoryProducts(categoryParam).
I tried to look on some others questions to solve this but no success. Code below, if need some more info, please let me know!
FYK: I need to do using Class component.
import React, { Fragment } from "react";
import { getProductsId } from "../services/product";
import { getCategoriesList } from "../services/categories";
//import styled from "styled-components";
class ProductListing extends React.Component {
constructor(props) {
super(props);
this.state = {
category: { data: { categories: [] } },
product: { data: { categories: [] } },
filteredProduct: { data: { categories: [] } },
};
this.handleEvent = this.handleEvent.bind(this);
}
async handleEvent(event) {
var prodArr = [];
const testName = event.target.id;
const testTwo = this.buildFilteredCategoryProducts(testName);
await this.setState({ filteredProduct: { data: testTwo } });
this.state.filteredProduct.data.map((item) => {
prodArr.push(item.key);
});
console.log(prodArr);
return prodArr;
}
async componentDidMount() {
const categoriesResponse = await getCategoriesList();
const productsResponse = await getProductsId();
this.setState({ category: { data: categoriesResponse } });
this.setState({ product: { data: productsResponse } });
}
getCategories() {
return this.state.category.data.categories.map((element) => {
const elName = element.name;
return (
<button id={elName} key={elName} onClick={this.handleEvent}>
{elName.toUpperCase()}
</button>
);
});
}
buildFilteredCategoryProducts(categoryParam) {
const filteredCategories = this.state.product.data.categories.filter(
(fil) => fil.name === categoryParam
);
let categoryProducts = [];
filteredCategories.forEach((category) => {
category.products.forEach((product) => {
const categoryProduct = (
<div key={product.id}>{`${category.name} ${product.id}`}</div>
);
categoryProducts.push(categoryProduct);
});
});
return categoryProducts;
}
buildCategoryProducts() {
const filteredCategories = this.state.product.data.categories;
let categoryProducts = [];
filteredCategories.forEach((category) => {
category.products.forEach((product) => {
const categoryProduct = (
<div key={product.id}>{`${category.name} ${product.id}`}</div>
);
categoryProducts.push(categoryProduct);
});
});
return categoryProducts;
}
buildProductArr() {
for (let i = 0; i <= this.state.filteredProduct.data.length; i++) {
return this.state.filteredProduct.data[i];
}
}
render() {
return (
<Fragment>
<div>{this.getCategories()}</div>
<div>{this.buildProductArr()}</div>
</Fragment>
);
}
}
export default ProductListing;
Ok, so this won't necessarily directly solve your problem,
but I will give you some pointers that would definitely improve some of your code and hopefully will strengthen your knowledge regarding how state works in React.
So first of all, I see that you tried to use await before a certain setState.
I understand the confusion, as setting the state in React works like an async function, but it operates differently and using await won't really do anything here.
So basically, what we want to do in-order to act upon a change of a certain piece of state, is to use the componentDidUpdate function, which automatically runs every time the component re-renders (i.e. - whenever there is a change in the value of the state or props of the component).
Note: this is different for function components, but that's a different topic.
It should look like this:
componentDidUpdate() {
// Whatever we want to happen when the component re-renders.
}
Secondly, and this is implied from the previous point.
Since setState acts like an async function, doing setState and console.log(this.state) right after it, will likely print the value of the previous state snapshot, as the state actually hasn't finished setting by the time the console.log runs.
Next up, and this is an important one.
Whenever you set the state, you should spread the current state value into it.
Becuase what you're doing right now, is overwriting the value of the state everytime you set it.
Example:
this.setState({
...this.state, // adds the entire current value of the state.
filteredProduct: { // changes only filteredProduct.
...filteredProduct, // adds the current value of filteredProduct.
data: testTwo
},
});
Now obviously if filteredProduct doesn't contain any more keys besides data then you don't really have to spread it, as the result would be the same.
But IMO it's a good practice to spread it anyway, in-case you add more keys to that object structure at some point, because then you would have to refactor your entire code and fix it accordingly.
Final tip, and this one is purely aesthetic becuase React implements a technique called "batching", in-which it tries to combine multiple setState calls into one.
But still, instead of this:
this.setState({ category: { data: categoriesResponse } });
this.setState({ product: { data: productsResponse } });
You can do this:
this.setState({
...this.state,
category: {
...this.state.category,
data: categoriesResponse,
}
product: {
...this.state.product,
data: productsResponse,
},
})
Edit:
Forgot to mention two important things.
The first is that componentDidUpdate actually has built-in params, which could be useful in many cases.
The params are prevProps (props before re-render) and prevState (state before re-render).
Can be used like so:
componentDidUpdate(prevProps, prevState) {
if (prevState.text !== this.state.text) {
// Write logic here.
}
}
Secondly, you don't actually have to use componentDidUpdate in cases like these, because setState actually accepts a second param that is a callback that runs specifically after the state finished updating.
Example:
this.setState({
...this.state,
filteredProduct: {
...this.state.filteredProduct,
data: testTwo
}
}, () => {
// Whatever we want to do after this setState has finished.
});

How to render updated state in react Js?

I am working on React Js in class component I am declaring some states and then getting data from API and changing that state to new value but React is not rendering that new value of state. but if I console.log() that state it gives me new value on console.
My code
class Home extends Component {
constructor(props) {
super(props);
this.state = {
unread: 0,
}
this.getUnread()
}
getUnread = async () => {
let data = await Chatapi.get(`count/${this.props.auth.user.id}/`).then(({ data }) => data);
this.setState({ unread: data.count });
console.log(this.state.unread)
}
render() {
const { auth } = this.props;
return (
<div>
{this.state.unread}
</div>
)
}
This is printing 2 on console but rendering 0 on screen. How can I get updated state(2) to render on screen.
and if I visit another page and then return to this page then it is rendering new value of state (2).
Please call getUnread() function in componentDidMount, something like this
componentDidMount() {
this.getUnread()
}
This is because in React class components, while calling setState you it is safer to not directly pass a value to set the state (and hence, re-render the component). This is because what happens that the state is set as commanded, but when the component is rerendered, once again the state is set back to initial value and that is what gets rendered
You can read this issue and its solution as given in react docs
You pass a function that sets the value.
So, code for setState would be
this.setState((state) => { unread: data.count });
Hence, your updated code would be :
class Home extends Component {
constructor(props) {
super(props);
this.state = {
unread: 0,
}
this.getUnread()
}
getUnread = async () => {
let data = await Chatapi.get(`count/${this.props.auth.user.id}/`).then(({ data }) => data);
this.setState((state) => { unread: data.count });
console.log(this.state.unread)
}
render() {
const { auth } = this.props;
return (
<div>
{this.state.unread}
</div>
)
}

React: Passing a value from componentDidMount to render()

I am new to react and getting confused between react hooks. There are many similar questions asked and I tried a few answers but it didn't work. I am trying to use a value of flag which has been set in componentDidmount() in render(). But I am getting undefined. Here is my code. Can someone help me?
export default class Shop extends Component {
constructor(props) {
super(props);
this.state = {
isContentTypeShop1: false,
};
}
async componentDidMount() {
const basketContextData = await fetchBasketContext(); // returns json object
const basketContentType = basketContextData.basketContentType; //returns string 'shop1'
console.log(basketContentType)
if(basketContentType === 'shop1') {
this.isContentTypeShop1 = true;
}
console.log(this.isContentTypeShop1); // returns true
}
render() {
console.log(this.isContentTypeShop1); //returns undefined
return (
<ul className="progress-bar">
<li>
{(this.isContentTypeShop1) && ( // hence doesn't work
<span>
Shop 1
</span>
)}
</li>
</ul>
);
}
}
You need to make use of setState to trigger a re-render from componentDidMount. Also isContentTypeShop1 isn't a class variable but its a state
async componentDidMount() {
const basketContextData = await fetchBasketContext(); // returns json object
const basketContentType = basketContextData.basketContentType; //returns string 'shop1'
console.log(basketContentType)
if(basketContentType === 'shop1') {
this.setState({isContentTypeShop1: true});
}
}
render() {
// use it from state
console.log(this.state.isContentTypeShop1);
}
this.isContentTypeShop1 doesn't exist because isContentTypeShop1 is inside state. Try this instead:
console.log(this.state.isContentTypeShop1);
And to update isContentTypeShop1, you need to call setState:
this.setState({ isContentTypeShop1: true });
You need to use this.state.isContentTypeShop1 instead of this.isContentTypeShop1 & you can't set state using =
You need to use setState like this.setState({ isContentTypeShop1: true })
You need to read Using State Correctly part from the React docs
And for some additional reading :)

How to cache react component in client side?

Is there a way to cache react component in client side. If a user comes a page say A and then navigate to another page say B, when again he comes back to A I want render should not execute ,no api call should be executed, the page should be served from cache .
You can cache state
let cachedState = null;
class ExampleComponent extend React.Component {
constructor(props) {
super(props);
this.state = cachedState !== null ? cachedState : {
apiData: null
}
}
componentWillUnmount() {
cachedState = this.state;
}
componentDidMount() {
if (this.state.apiData === null) {
this.loadApiData()
.then(apiData => {
this.setState({apiData});
});
}
}
loadApiData = () => {
// code to load apiData
};
}
As long as the input props are not getting changed, you can use React.memo().
(This is not useMemo Hook. Please don't get confused)
const Greeting = React.memo(props => {
console.log("Greeting Comp render");
return <h1>Hi {props.name}!</h1>;
});
Read this article for further clarifications -> https://linguinecode.com/post/prevent-re-renders-react-functional-components-react-memo

React-Apollo - render getting called before getDerivedStateFromProps

I have a component that includes a verification form for a user. This component runs a graphql query upon mount. In the Verification component, I need to use the data from the graphql query to set the state so I can use it to update any values, and then submit them with the form. I have since learned about getDerivedStateFromProps and that is working to populate a new state from that data. But, the data isn't available in the DOM. It's as if render gets called before getDerivedStateFromProps.
Here is the component:
class Verification extends React.Component {
constructor(props) {
super(props);
this.state = {
company: {
legalName: ''
},
openCorporatesCompany: {}
};
}
handleLegalNameChange = (legalName) => {
let company = _.cloneDeep(this.state.company);
company.legalName = legalName;
this.setState({
company
})
};
static getDerivedStateFromProps(next, prev) {
let newState = _.cloneDeep(prev);
let {openCorporates: {getEntityAttribute}} = next;
if (getEntityAttribute && getEntityAttribute.value) {
let openCorporatesCompany = JSON.parse(getEntityAttribute.value);
let company = _.cloneDeep(newState.company);
company.legalName = openCorporatesCompany.name;
newState.openCorporatesCompany = openCorporatesCompany;
newState.company = company;
return newState;
}
return null;
}
render() {
console.log(this.state);
return (
<Input
label='Legal Name'
placeholder='Legal entity name...'
type='text'
subtext='Use the name your customers or clients will recognize'
onChange={this.handleLegalNameChange}
value={this.state.legalName}
/>
);
}
}
export const VerificationContainer = compose(
connect(mapStateToProps, mapDispatchToProps)
graphql(GetEntityAttributeQuery, {
name: "openCorporates",
options: (props) => ({
variables: {
entityId: props.currentEntity.id,
type: EntityAttributes.TypeOpenCorporates
}
})
})
)(Verification);
The console output of the console.log(this.state) in render looks like this:
As you can see, the field gets updated in state in company.legalName. But, it never gets populated in the Input box:
Why does the input not get updated with the new state? It's as if the render gets called before getDerivedStateFromProps.
I know your struggle with react and component updating, but I guess there's no bullet proof to get rid of it; 80% I guess you should try any of the different life cycle methods. Back in the day there was componentWillReceiveProps for async calls but since it's been marked unsafe (I guess) you should try getDerivedStateFromProps(props, state)
getDerivedStateFromProps(props, state) {
console.log("*************** PROPS:", props);
let { openCorporates: { getEntityAttribute } } = props;
if (getEntityAttribute && getEntityAttribute.value) {
let openCorporatesCompany = JSON.parse(getEntityAttribute.value);
let company = _.cloneDeep(this.state.company);
company.legalName = openCorporatesCompany.name;
this.setState({
openCorporatesCompany,
company
})
}
}
Take into account I haven't run the snippet.
This ended up doing the trick, thanks to #Charlie's answer:
static getDerivedStateFromProps(props, state) {
let newState = _.cloneDeep(state);
let { openCorporates: { getEntityAttribute } } = props;
if (getEntityAttribute && getEntityAttribute.value) {
let openCorporatesCompany = JSON.parse(getEntityAttribute.value);
let company = _.cloneDeep(newState.company);
company.legalName = openCorporatesCompany.name;
newState.openCorporatesCompany = openCorporatesCompany;
newState.company = company;
}
return newState;
}

Resources