Render an array as list with onClick buttons - reactjs

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.
});

Related

how to make looping in the React state?

I will make a loop a state in react, but is not as expected:
state={
product:[
{
frontImage:"frontImage1.jpg",backImage:"backImage1.png"
},
{
frontImage:"frontImage2.jpg",backImage:"backImage2.png"
}
]
}
I try to use code like this
state = {
product: this.props.location.state.colorPick.map(color => {
frontImage:null,
backImage:null
})
};
but the results are like this:
{0: undefined1: undefined}
If you want to use props to generate your desired output, an array of objects [{...}]
state = {
product:[
{frontImage:"frontImage1.jpg",backImage:"backImage1.png"},
{frontImage:"frontImage2.jpg",backImage:"backImage2.png"}
]
}
Consider handling this logic in componentDidMount() instead. If you try to create the array during the state-initialization, its not a guarantee that the expected prop-values will be passed down before the component first renders. That's where componentDidMount() comes in, it executes logic AFTER the component has rendered once and we can expect props to be passed by then.
Try something like:
componentDidMount(){
const newProduct = this.props.location.state.colorPick.map(() => {
return {
frontImage:null,
backImage:null
}
})
}
Additionally, in your .map(), you are not using a short-hand, and you never explicitly called return so you would not get the desired object.

How to migrate componentWillReceiveProps in react 16.0.0?

I have a reactcomponent that has a few obsolete events:
componentWillMount() {
const { applicationStages } = this.props;
if (applicationStages && applicationStages.length > 0) {
this.setState({
appColumnsSorted: this.getSortedAppColumns(someVar),
});
}
}
componentWillReceiveProps(nextProps) {
const {
presets: { sortCriteria: sortBy, customCriteria },
} = nextProps;
const { appColumnsSorted } = this.state;
const sortedColumnsUpdated = this.getSortedAppColumns(
appColumnsSorted,
sortBy,
true
);
this.setState({
appColumnsSorted: sortedColumnsUpdated,
});
}
getSortedAppColumns = (appColumns, sortBy, criticalFirst) => {
//returns object
};
'componentWillMount' is basically to initialize the appColumnsSorted. The issue is that with v16 this event is obsolete. So what can event can I use for this now? Also what is the way to migrate 'componentWillReceiveProps' in this scenario?
What you're using componentWillMount for can be done in the constructor so
componentWillMount() {
const { applicationStages } = this.props;
if (applicationStages && applicationStages.length > 0) {
this.setState({
appColumnsSorted: this.getSortedAppColumns(someVar),
});
}
}
will change to
export default class YourClass extends Component {
constructor(props) {
// keep a separate method just to avoid writing code in constructor for readability
this.state = constructInitialState(props);
}
constructInitialState(props) {
const state={};
//More state handling as required
const { applicationStages } = props;
if (applicationStages && applicationStages.length > 0) {
state.appColumnsSorted = this.getSortedAppColumns(someVar);
}
return state;
}
}
This approach is slightly better because getDerivedStateFromProps will be called before each render and will waste computation.
From the code snippet it is not obvious why you want to store it in state. If you do save it to state then the only way you have would be to use componentDidUpdate as mentioned in the other answer by Aaditya Thakkar. This will require you to mirror your props in state only for comparison purpose (Mapping props to state is not the best way, more on this link https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#anti-pattern-unconditionally-copying-props-to-state)
I would, however, recommend calling your sort computation method and return its result in render directly; this would avoid extra checks in componentDidUpdate to render the second time. As I'm not aware if these props are coming from redux or a parent React component, another option would be to avoid expensive computation in this class and simply provide the correct value from either the parent component or calculate the value in the redux store and send the final prop directly for use.
ComponentWillReceiveProps can be replaced with getDerivedStateFromProps. getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates. It should return an object to update the state. It's a static method, so this can not be used inside it.
Hence, you can no longer reference this.getSortedAppColumns from getDerivedStateToProps, you need componentDidUpdate lifecycle for that. Here, I have broken down ComponentWillReceiveProps into getDerivedStateFromProps and componentDidUpdate:
static getDerivedStateFromProps(nextProps, prevState) {
const {
presets: { sortCriteria: sortBy },
} = nextProps;
if (sortBy === prevState.sortBy) return null;
return ({ sortBy: nextProps.sortBy });
}
componentDidUpdate(_, prevState) {
const { appColumnsSorted, sortBy } = this.state;
if (sortBy !== prevState.sortBy) {
const sortedColumnsUpdated = this.getSortedAppColumns(
appColumnsSorted,
sortBy,
true
);
this.setState({
appColumnsSorted: sortedColumnsUpdated,
});
}
}

function returning data but not showing

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>)

Setting state value in react native

Here is what I am trying to do and examples I have found don't seem to set the value either. So I am sure it is something simple I am over-looking.
setViewedSubmission(subId: string) {
logger.log("pre:", this.state.position.candidates.find((c)=> {
return c.submissionId == subId
}).viewedSubmission) //returns original value
this.state.position.candidates.find((c)=> {
return c.submissionId == subId
}).viewedSubmission = true //expect the value to update
this.forceUpdate()
logger.log("post:", this.state.position.candidates.find((c)=> {
return c.submissionId == subId
}).viewedSubmission) //returns original value still
}
This is what I actually started with but also doesn't work: Based on the responses, this is closer to what I should be doing but still doesn't. What is the issue here?
let pos = Object.assign({}, this.state.position)
pos.candidates.find((c) => {
return c.submissionId == subId
}).viewedSubmission = true;
this.setState({ position: pos })
The only way to update the state and get the screen re-rendered is to use the setState method. Here a look:
let candidates = this.state.position.candidates;
candidates = candidates.map(candidate => {
if(candidate.submissionId === subId) candidate.viewedSubmission = true;
return candidate;
});
this.setState(prevState => ({
position: {
...prevState.position,
candidates
}
}));
I think you're looking at this backwards. React is basically a state machine which means the UI reflects the current status of the state.
Without delving too deep into your code you probably want to structure things something similar to the following:
class Demo extends React.Component {
constructor(props){
super(props)
this.state = {
viewed: false,
}
}
someBusinessLogic(viewed) {
this.setState({
...this.state,
viewed: viewed,
})
}
render() {
const { viewed } = this.state
if (viewed ) {
return <div>VIEWED</dive>
} else {
return <div>UNVIEWED</div>
}
}
}
...and somewhere else
...//this is terrible code, just an example of using the business logic
<Demo viewed='true' ref={r => this.demo1 = r} />
<Demo viewed='false'/>
<button onClick={e => { this.demo1.someBusinessLogic('viewed') }}>Set Viewed = 'viewed'</button>
...
Structuring the component this way means that you reflect upon the state rather than trying to manage when updates occur. setState(newState) will re-render if the state is different.
My guess is that the internal state of your component is the same.
If I misread your question and you are not seeing updates passed to the component make sure you are using the key and that it is updating or override shouldComponentUpdate which is where forceUpdate starts to be useful.
Hopefully this helps, you might consider providing more details about your implementation so the community can be more specific.
You can't alter state directly. It is immutable (If you plan on working with React, you should read more about that). When you say this.state.value = 1, it won't update. Instead, you should do this.setState({ value: 1 }). And, if there are more itens in state, you should do this.setState(p => ({ ...p, value: 1 }), since when you update state, you must return the entire new state object.

Hold the component's rendering until the store has finished hydration

I'm fetching my initial data like so:
export function populate (dispatch) {
let posts = []
dispatch(requestNews())
fetch(someEndPoint)
.then(payload => payload.json())
.then(items => {
//some fetching logic that populates posts list above
})
.then(() => { dispatch(receiveNews(posts)) })
.catch(err => {
console.log(err)
})
}
function request () {
return {
type: REQUEST_NEWS,
payload: {
populating: true
}
}
}
function receive (posts) {
return {
type: RECEIVE_NEWS,
payload: {
populating: false,
posts
}
}
}
As you can see above I'm setting the store with a field called populating which starts as false and changes to true when the 'request' is dispatched and then back to false when 'received' is dispatched.
Then my component looks something like the following:
import { populateNews } from '../modules/news'
class News extends React.Component {
constructor (props) {
super(props)
//this.mapPosts = this.mapPosts.bind(this)
}
componentWillMount () {
populateNews(this.props.dispatch)
}
render () {
if (!this.props.news.populating) {
return (
<div>
{this.props.news.posts[0].title}
</div>
)
} else {
return (
<div>loading</div>
)
}
}
}
On initial load render is being called before the store is populated with the fetched posts even though my populate switch changes back and forth as expected.
I've tried dealing with it using a local state on the component, so it's constructor has: this.state = {populating: false} and then the action creator changes that, but got the same result.
So at the moment my solution is to instead check if the state has a slice called 'posts' which is being created after the content is fetched and if it does to render it. like so:
render () {
return (
<div>
{this.props.news.posts ? <div>{this.props.news.posts[0].title}</div> : null }
</div>
)
}
This of course just renders the component and then renders it again after the store is updated with the posts, and is not an optimal solution like waiting with the render until the fetch is completed and the store is populated.
There's a long discussion about it here:
https://github.com/reactjs/react-redux/issues/210
How can I delay or better put condition the render itself?
Hmm, you might want to change your if statement. Unless your store is initializing populating to true, it will be undefined on the initial load and will pass your if (!this.props.news.populating) validation which will try to render the post title. Change your condition to look for a truthy value rather than a falsey value and you should have more control over it.

Resources