How to store the information in react component. - reactjs

When I am asking this question, lots of doubts are coming into my mind. well, first I will give my problem description.
I have component X. and it contains checkboxes and a search box.
while something typed (call it search_query) in search box,
X needed to update the checkboxes which matches the search_query. [note that I got all the values of checkboxes by some api call. and it is done when component created. ]
First doubts I came to my mind is that
store (search_query) and (values of checkboxes) in component state
if the values are more searching takes more time.
is it possible to change the values of props inside the component
or is there any other way to do it ??

Since no code is shared. Assuming you are using plain React ( no Redux, middlewares are used ).
Answering your question:
[1] Is it possible to change the values of props inside the component?
Changing props values inside the component is a bad approach.
"All React components must act like pure functions with respect to their props."
[ref: https://facebook.github.io/react/docs/components-and-props.html#props-are-read-only]
Also, the view doesn't get the update if props values changed within the component.
[2] or is there any other way to do it.
yes ( without mutation inside the component )
using "state" property to hold values & setState to update values.
[3] How to store the information in react component?
Let's rename component X as FilterData,
searchbox ( SearchBox ) & checkboxes (SelectionBox) are two individual components.
// Define state to FilterData
class FilterData extends React.Component {
constructor() {
super()
this.state = {
term: '',
searchResults: []
}
}
....
}
// pass the state values to child components as props
class FilterData extends React.Component {
....
render() {
return (
<div>
<SearchBox term={this.state.term} />
<SelectionBox options={this.state.searchResults} />
</div>
)
}
}
In React App,
data flows top down (unidirectional) and there should be a single source of truth.
SearchBox & SelectionBox are two individual (sibling) components,
SearchBox's state has terms ( which has the search string )
When the user enters input SearchBox will update its state and possible to detect change event and fire ajax and get the response.
How SelectionBox can detect search that had happened, how it can get data.
This is why the state is moved to common ancestor FilterData.
[Ref: https://facebook.github.io/react/docs/lifting-state-up.html]
[Ref: https://facebook.github.io/react/docs/state-and-lifecycle.html#the-data-flows-down]
Code Sample -----------------------------------------------------
Selected values are not saved:
https://codepen.io/sudhnk/pen/NgWgqe?editors=1010
Selected values are saved:
https://codepen.io/sudhnk/pen/JJjyZw?editors=1010

Related

MultiSelect does not update value when value state changes (PrimeReact UI)

https://www.primefaces.org/primereact/showcase/#/datatable
https://www.primefaces.org/primereact/showcase/#/multiselect
I am using the PrimeReact library to create and customize a Data Table.
My table is dynamic and will build itself based on the data given to it. I am assigning different filters to each column depending on the column's data type, but because there are a variable number of columns I must create the filters dynamically.
To accomplish this I am factoring out the filter logic into a separate class which contain their state and logic.
My issue is that the MultiSelect component I am using as a filter interface does not update its value when it's value's state is updated. After updating the state the value remains null. As the MultiSelect component does not have a reference to the previously selected values I can only choose one value at a time.
I think I am missing some understanding regarding class components, as I usually use functional components. I used a class component in this case so that I could access filterElement from the instantiated DropDownFilter class through DropDownFilter.filterElement() and use as a prop in the Column component.
import React from 'react'
import { MultiSelect } from 'primereact/multiselect';
class DropDownFilter extends React.Component {
constructor(props) {
super(props);
this.multiSelRef = React.createRef();
this.state = {
selectedOptions: [],
}
// Added following two lines trying to fix issue but they did not alter behaviour
this.onOptionsChange = this.onOptionsChange.bind(this)
this.filterElement = this.filterElement.bind(this)
}
onOptionsChange = (e) => {
this.props.dt.current.filter(e.value, this.props.field, 'in');
this.setState({selectedOptions : e.value})
}
filterElement = () => {
return (
<React.Fragment>
<MultiSelect
ref={this.multiSelRef}
value={this.state.selectedOptions} //*** value is null even when selectedOptions has been updated after user chooses option.
// Confirmed by viewing value through multiSelRef
options={this.props.options}
onChange={this.onOptionsChange}
optionLabel="option"
optionValue="option"
className="p-column-filter"
/>
</React.Fragment>
)
}
}
export default DropDownFilter;
I learned the state was not working in this case because I was instantiating the DropDownFilter using the new keyword in when using it. This means it wasnt being mounted to the DOM and could not use state.
I am still having issues regarding implementing the custom columns with filters, but I have posted a new question to handle that scope with my new findings.

How can I make sure a React parent component loads data before its child component mounts?

I'm writing a simple calendar application that uses a common layout to wrap different views of events (month view shows a larger calendar with all the days of the month and events for each day, week view just shows a vertical list of events for that week, etc.). The common layout includes a calendar picker control for selecting the date, and then a list of event categories that can be checked or unchecked to show events relating to sports, entertainment, etc.
When the layout mounts, I'm calling an async Redux action creator to get the list of event categories from the database. When those are retrieved, they're saved in a Redux store with a property of Selected set to true, since they're all selected at initial load.
async componentWillMount() {
await this.props.getEventTypes();
}
When the month view, which is a child of the layout view, mounts, it's grabbing all the events for the given month. Part of the selection process of getting those events is sending the list of selected event categories to the backend so it only gets events from the selected categories.
async componentWillMount() {
await this.props.getWeeks();
}
The problem is, the selected categories list is always empty when the month view goes to grab the events for the month. So it's not going to select anything since no categories are selected.
It seems the only way this can be happening is if the child component is mounting first, or if the parent component is taking so long to get the event categories that the getWeeks process finishes first (this is unlikely as the process to grab the weeks and days and their events is much more involved than just selecting the event category list).
So, how can I make sure the parent component grabs the event categories from the database and puts them in the Redux store before the child component selects its events?
I know one way, probably the best way, to do this would be to have the list of event categories render into the page on the server side, so it's just already present at initial load. I'll probably end up doing it that way, but I'd also like to know how to do it all through client-side actions, in case I need to do it that way for some reason in the future.
You can try like this
Set isDataLoaded when data is available.
Use ternary operator for conditional rendering.
In you render
return(
<>
....
{ isDataLoaded ? <ChildComponent /> : null }
....other sutff
</>
);
Use can also use the && operator
return(
<>
....
{ isDataLoaded && <ChildComponent /> }
....other sutff
</>
);
You can integrate componentDidUpdate() and use it to render your child-components in a somewhat synchronous flow.
Let's say the structure of your Parent Component should look something like the following:
Parent
class Parent extends React.Component{
state = {
renderChildren: false
}
async componentDidMount() {
await this.props.getEventTypes();
}
componentDidUpdate(prevProps){
if(this.props.yourUpdatedReducer !== prevProps.yourUpdatedReducer){
this.setState({
renderChildren: true
})
}
}
render(){
const { renderChildren } = this.state
return(
{ renderChildren ? <Child/> : "Loading" }
)
}
}
You want a key in your state that determines whether you should
render the Child component.
In componentDidMount(), you call the action-creator function, when
it completes, you get updated props.
With updated props, you trigger componentDidUpdate(), where you
check the values in your reducer. If the values are
different that means you got the updated data from your database, so
everything has loaded.
Great, so now you want to mount your Child component, so you
update-state, setting renderChildren to true, thus re-rendering the
Parent component. Now Child gets rendered and should behave as expected.

Part of the redux state re-renders (unnecessary) whole react component

One of my redux state part (reducer) is filters reducer, used to store filter settings per page. It's key-object structure, so it looks like this:
{
dashboard: {
minDate: '2017-01-01',
maxDate: '2019-01-01',
//... other filters
},
otherPageKey: {
//... other filters
}
}
My dashboard page is big, but it contains select which value is read from reducer: filters.dashboard.minDate. Code that is responsible for connection:
function mapStateToProps({
filters
}) {
return {
filters: filters.dashboard
};
}
Now - whenever we select new date from that select component, whole filters tree is changed, so the whole dashboard component is being re-renderd.
How can I solve this problem? Expected result is, that only select component, whose property is changed by user, should be re-rendered.
More code would be helpful, but this is usually caused by the state or props being detected by a parent component (in this case, dashboard).
This can be caused by a few things.
You're mapping to parent component rather than the specific child one. This means the parent component is detecting the change and therefore re-rendering. Is your mapStateToProps in your dashboard component as opposed to just in your select component?
Improper use of connect or mapDispatchToProps. Check out this post for more information.
Again, more code would be helpful but I hope this helps.

Best way to pass same data to two React components?

I currently have two React components one that maintains a user inputted list of objects ex: [{"good1":"$10"},{"good2":"$15"}] and another component that lets the user create a list and use the goods as tags. However I am having trouble on how to pass data that is in the state of GoodManage to ListManager, I also have a wrapper for the layout. I tried passing the state from the LayoutManager into the GoodManager and having it update the goods list, and send that same list to ListManager, but it only seems to work once.
class LayoutManager extends React.Component{
constructor(){
super();
this.state = {"goods":[{"good1":"$1"}]};
}
updateGoods(goods){
this.setState({"goods":goods});
}
render(){
return (
<div className="layout-manager">
<div className="good-manager container">
<GoodManager update={this.updateGoods.bind(this)} goods={this.state.goods}/>
</div>
<div className="list-mananger container">
<ListManager goods={this.state.goods}/>
</div>
</div>
)
}
}
As you described its working only first time, it means you are storing the goods value in the state variable of ListManager component in constructor, it will save only the initial list since constructor only get called on first rendering not on re-endering, so you need to use lifecycle method componentWillReceiveProps, it will get called when any change happen to props values, at that time you need to update the state values, use this method in ListManager component:
componentWillReceiveProps(nextProp){
this.setState({goods: nextProps.goods});
}
Reference: https://facebook.github.io/react/docs/react-component.html#componentwillreceiveprops

In componentDidUpdate refs is undefined

I want to use Chart.js on my website. As you can see title, I'm using React.js. To use Chart.js, I need the canvas and context like this:
let context = document.getElementById('canvas').getContext('2d');
let chart = new Chart(context, ...);
so I design the component like this:
export function updateChart() {
let context = this.refs.chart.getContext('2d');
let chart = new Chart(context ,... );
...
}
export default class GraphChart extends React.Component {
constructor() {
super();
updateChart = updateChart.bind(this);
}
componentDidMount() {
updateChart();
}
render() {
return <canvas ref="chart" className="chart"></canvas>;
}
}
as you can see, I exported two things, update chart function and GraphChart class. Both will using in parent component like this:
import { updateChart } from './GraphChart';
import GraphChart from './GraphChart';
class Graph extends React.Component {
...
someKindOfAction() {
// update chart from here!
updateChart();
}
render() {
return (
<div>
<SomeOtherComponents />
<GraphChart />
</div>
);
}
}
then Parent class using exported updateChart function to update chart directly. It was working, but only first time. After unmount and mount the GraphChart component, it's refs are just empty.
Why refs is empty? And If I did wrong way, how can I get canvas context for initialize Chart.js?
Object refs is undefined, because this is not what you think it is. Try logging it.
The function you’re exporting is not bound to this of your component. Or perhaps it is, but to the last created instance of your component. You can never be sure that’s the mounted instance. And even if you are, you can not use multiple instances at the same time. So, I would dismiss this approach entirely.
Other than that, providing the function to alter some component’s state is exactly the opposite of what’s React is trying to accomplish. The very basic idea is that the component should know to render itself given some properties.
The problem you are trying to solve lies in the nature of Canvas API, which is procedural. Your goal is to bridge the gap between declarative (React) and procedural (Canvas) code.
There are some libraries which do exactly that. Have you tried react-chartjs? https://github.com/reactjs/react-chartjs
Anyways, if you’re wondering how the hell should you implement it the “React way”, the key is to declare properties your component handles (not necessarily, but preferably), and then to use component lifecycle methods (e.g. componentWillReceiveProps and others) to detect when properties change and act accordingly (perform changes to the canvas).
Hope this helps! Good luck!

Resources