I have set up a searchbar as the child component and pass it two props, one is the array of cards that I am rendering and the other is function, which should setState as the search field changes.
<Searchbar quesCards={questionCards} filter={this.filter} /> :
Now this questionCards is a state variable containing the array of cards to be displayed.
questionCards.push(
<QuestionCard
id={i}
question={question.question}
answer={question.answer}
isMarked={question.isMarked}
isPublished={question.isPublished}
validity={question.validity}
category={question.category}
/>
)
this.setState({ questionCards, newCards: questionCards, loading: false });
Here <QuestionCard /> is another component, but I believe it has nothing to do with the issue.
Inside my child component, I call this.props.filter with the filtered results like this:
handleChange = name => event => {
//filtering the data ...
this.props.filter(newCards);
}
This I do inside onChange function of <Searchbar/>
Back in the Parent Component,
filter = (newCards) => {
console.log(newCards); //displays the desired result
this.setState({ newCards }); //the state changes, but is not rendered on screen
}
After this setState, the component should re-render with the new results. But it doesn't happen so.
Please note that it is newCards which is getting displayed, since the beginning and questionCards is used only to pass as prop
I have already tried this.forceUpdate
Also, if I console log inside the function filter, newCards contains correct results, but it is not getting re-rendered after this.setState({ newCards })
I think the problem is that your data flow is inefficient.
Your filter input within searchBar, should update some filter state (locally). It looks like just need to filter the questions from your filter state.
The consumer of <SearchBar /> does not need to know the filter.
const list = questions.filter(item => (
// item.name[enter_property] === this.state.filter
))
Then just .map() over list.
—
What condition are you matching? A specific property or multiple of them?
You are generating QuestionCard like this
questionCards.push(
<QuestionCard
id={i}
question={question.question}
answer={question.answer}
isMarked={question.isMarked}
isPublished={question.isPublished}
validity={question.validity}
category={question.category}
/>
)
But ,your updating the newCards state variable ,can u check that?
If its not resolved , can u post your full code.
Related
I have a react app that renders a list of cards in a container widget. The container widget has a useEffect where we subscribe to an observable and update the array that is then used to render the cards inside the component. Each time any of the cards change, the observable emits new values resulting in creating of the array all over again and thus all the cards are re-rendered. However this re-render causes a noticeable UI lag.
Here is the stripped down version of code from the container component.
const obsRef = useRef<Subscription>(null);
const [apiResArray, setApiResArray] = useState([]);
useEffect(() => {
if(obsRef.current) obsRef.current.unsubscribe();
const mySubscription = myObservable$(changingValue)
.subscribe((val) => {
setApiResArray(val.apiArray);
});
obsRef.current = mySubscription;
return () => {
obsRef.current && obsRef.current.unsubscribe();
};
}, [changingValue])
return (
<CardWrapper $showMenu={showMenu}>
{ apiResArray.map((res, resIndex) => {
return (
<Card
data={{
// a json object with props
}}
key={res?.hKey}
/>
);
})}
</CardWrapper>
);
The Card component here simply renders the content based on props passed to it. I know that since data is an Object, referential equality may fail and I have tried memoizing the component but even that does not help.
There is a lot more to these components but posting all the code won't make sense. I wish to understand what possibly might be causing the list re-render to be such a heavy operation that the whole UI gets stuck for a second or two. The array contains around only 100 objects or so. It happens whenever changingValue changes. I can share more information as required.
Any suggestions on improving the performance are highly appreciated.
React will mount and unmount the component in DEV mode to validate effect cleaner and any side effect.
I'm concerned about that subscribe and unsubscribe in your effect.
If you want changingValue to be defined why not just wrap it inside an if statement ?
if (changingValue) {
const mySubscription = myObservable$(changingValue)
.subscribe((val) => {
setApiResArray(val.apiArray);
});
obsRef.current = mySubscription;
}
Another observation is at render of items
<Card
data={
{
// a json object with props
}
}
key={res?.hKey} // Why is this a falsy value ?
/>
Falsy value can make key prop change those re-rendering the component, one with React internal key value and another with your implementation value
I have a function which basically generates dynamic dom as below
const arrMarkup = [];
const getMarkup = () => {
{
if(true){
arrMarkup.push(<Accordion expanded={expanded === cust.name} onChange={handleChange(cust.name)}>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="panel1bh-content"
id="panel1bh-header"
>
<Typography className={salesEvent.name && classes[salesEvent.name]}></Typography>
</AccordionSummary>
</Accordion>
)
})
})
}
return <div>{arrMarkup}</div> ;
}
Now, i am trying to execute this function is on useEffect as below
useEffect(() => {
getMarkup();
}, [cust]);
and trying to add in return of JSX as
return (
<div>
{arrMarkup}
</div>
)
but can not see the markup added, however i can see its added in array arrMarkup. What's wrong?
React only re-renders your component when its state or props change. As far as one can tell from your question, arrMarkup isn't either a state member or a prop. (If it were, directly modifying it would be against the React rules, because you must not directly modify state or props.)
It's hard to tell you what to do without more information, but you probably want arrMarkup to be a state member, for instance (in a functional component using hooks):
const [arrMarkup, setArrMarkup] = useState([]);
and then to update it appropriately, for instance:
setArrMarkup(current => [...current, <NewStuff>goes here</NewStuff>]);
Note that I used the callback version of the state setter. That's important when you're updating state based on existing state, since state updates may be asynchronous and can be batched (so the state information you already have can be stale).
FWIW, a couple of other observations:
It's unusual to have the useEffect dependency be cust (a single object as far as I can tell) and have triggering the effect add an entry to an array that has previous entries for previous values of cust which (apparently) you're no longer storing in the component's state anywhere. That just feels very off, without more context.
You haven't shown the definition of handleChange, but onChange={handleChange(cust.name)} looks like it's probably incorrect. It calls handleChange, passing in cust.name, and the uses its return value os the change handler. Did you mean onChange={() => handleChange(cust.name)}, so that handleChange is called when the event occurs?
I'm having an array data.info that is being updated over time and I'm trying to replace placeholder rendered elements with another. So by default app.js looks like this
return (
<Fragment>
{data.info.map((index) => {
return <Typography key={index} variant="h6" className={classes.title}>Demo</Typography>
})}
</Fragment>
)
Also I have a hook with async function to subscribed to data.info.length.
useEffect(
() => {
if (!initialRender.current) {
if (data.info.length!==0) {
for (let i = data.info.length-iScrollAmount+1 ; i < data.info.length+1; i++) {
firstAsync(i)
}
}
} else {
initialRender.current = false
}
},
[data.info.length]
)
async function firstAsync(id) {
let promise = new Promise(() => {
setTimeout(() => console.log(document.getElementById(id)), 500)
});
}
With document.getElementById() and id I can get to every element that was rendered and change it. And here goes the problems.
I'm using material-ui so I can't get to <Typography/> because it is transformed into <h6/>. Probably that is not a problem since I need to replace contents, so I can find parent element and remove all children. Is that way correct?
After I delete children how do I add content using jsx? What I mean is that in async function I'll get an array that I want to use in new element <NewCard/> to dynamically put into <Fragment/>. Yet I did not find any example how to do that.
It is not a good practice to change DOM Nodes directly in React, and you need to let React do the rendering for you and you just tell react what to do.
in your case you need to define a React State for your data and set your state inside your firstAsync function and then use your state to render whatever html element or React component which you want
React does not encourage the practice of manipulating the HTML DOM nodes directly.
Basically you need to see 2 things.
State which is a special variable whose value is retained on subsequent refresh. Change in reference in this variable will trigger component and its children a refresh/re-render.
Props which is passed to every Component and is read only. Changing in props causes refresh of component by default.
In your example, based on data.info you want to render Typography component.
Solution
First thing is your map function is incorrect. First parameter of map function is item of list and second is index. If you are not sure if info will always be present in data, you may want to have a null check as well.
{(data.info || []).map((info, index) => {
return <Typography key={index} variant="h6" className={classes.title}>{info.text}</Typography>
})}
You should be passing info from map to Typography component. Or use info value in content of Typography as shown above.
Update data.info and Typography will update automatically. For this, please make sure, data.info is a component state and not a plain variable. Something like
const [data, setData] = React.useState({});
And when you have value of data (assuming from API), then
setData(responseApi);
I've building a page where it get a list of array of object from the server, and I have few checkbox as filter, user will check or uncheck the checkbox, I will have to filter the list, but I have problem rerender the list now. Below is my partial code.
filterItem () => {
// logic goes here
console.log(filteredItems)
}
renderItems (items) => {
return(
//map logic goes here
)
}
render(){
return(
{this.renderItems(this.props.item)}
)
}
how can I rerender renderItems function by passing the filteredItems as param to it? I tried this.renderItems(filteredItems) but did not see my list got updated.
The props or the state of your component does not change, so your component won't get re-rendered. You probably want to use state (See docs here) to store your data and change it accordingly so that your component will re-render.
You can set your items in state of component
constructor(){
super();
this.state = {
items: this.props.items
}
}
and render it with {this.renderItems(this.state.items)}. If i'm clear understand you, and when user click checkbox you filter and change your items array, will enough just setState this new array, and your component will rerender self.
https://facebook.github.io/react/docs/react-component.html
An update can be caused by changes to props or state.
You are not doing either
I am trying to create an filter list which filter the list on typed input. But don't know why the if statement is not working.
Here is the function I wrote: `
filterList (event) {
var updatedList = this.props.array;
var filterText = this.state.text;
updatedList = updatedList.filter(function(item){
console.log(filterText);
if(item.name === filterText){
console.log('check', item.name);
}
});
Can someone help me out on this here is a link to my codepen: LINK
Your filter needs to update the array in the component state to force a re-render. You don't update props to force a re-render, props are more for initial data, or consistent data, which can be updated from the top level.
Change the handle function to pass the text to the filterList function, and return the result
handleChange(event) {
var array = this.filterList(event.target.value);
this.setState({ text: event.target.value, array: array });
},
filterList (filterText) {
var updatedList = this.props.array;
return updatedList.filter(function(item){
return item.name === filterText;
});
}
Update getInitialState to use the props:
getInitialState() {
return { text:'', array: this.props.array};
}
Then use the state instead of props in your render:
var arrayComponents = this.state.array.map(function(photo) {
return <li className="photo photo-name">{photo.name} <img className="photo" src={photo.link}/></li>;
})
First, when I run your codepen example, I'm not seeing any errors indicating that this.state.text is undefined. It's not filtering anything, but it is displaying the value of this.state.text. So the issue is not quite what your question suggests it is...
So how to we get this thing filtering? Basically, the idea with ReactJS components is that everything related to the current state of your component should be in this.state, and any decisions about what to display based on that state should be in the render method. And keep in mind that any time you change the state using the setState method, it's going to trigger a re-rendering of your component.
With that in mind, I'd set things up like this:
your component receives a list of photos via the array prop (I'd probably call it something else, since it's not very descriptive and it's awfully close to a reserved word in Javascript)
the state for the component has one value: text (again, not very descriptive)
the component has a handleChange handler for the input element. I would connect this to the onChange handler for the input element - don't worry, it will be called every time the value in the input element changes. This should be the only event handler on the input element and all it should do is call this.setState({ text: event.target.value });
do the list filtering in the render method. The key here is that you don't need to keep a filtered list of your photos - all you're doing with it is rendering it, so do it only when you need to (or rather, when React thinks you need to and calls the render method). So your render method will look something like this:
render() {
var myFilteredList = this.props.array.filter(function(item){
console.log(filterText);
if(item.name === filterText){
console.log('check', item.name);
return true;
}
return false;
});
var arrayComponents = myFilteredList.map(function(photo) {
return <li className="photo photo-name">{photo.name} <img className="photo" src={photo.link}/></li>;
});
return (
<div>
<h1>Hello, {this.props.name}</h1>
<p>{this.state.text}</p>
<input type="text" onKeyUp={this.handleChange} />
<ul>
{arrayComponents}
</ul>
</div>
);
}
That's it. This has a couple of advantages:
it keeps it simple - don't maintain any state for the component outside of this.state and if you only need it for rendering, don't maintain it at all.
it keeps your code cleaner - the way I've approached it, your component has only two methods: handleChange and render
it (should be) more performant - React is pretty good at deciding when to render based on state changes, and it tends to be better when components have minimal state. (I say "should be" simply because I haven't tested it out at all).