How can I add an unchangeable property to a React JS component? - reactjs

Suppose I have a React JS component, and for the sake of ease of use, I want that component to have access to an array that maps indexes to text.
For example, let's say that in scenario A I pass the component the ID 1, whereas in scenario B I pass the component the ID 3. I want the component to 'know' what to do with those IDs, so I want the component itself to have an array like the following:
[
0 => 'Text for scenario 0',
1 => 'Text for scenario 1',
2 => 'Text for scenario 2',
]
Now there are two ways that I personally know of that would allow this component to have access to that array. I could define it as part of the state in the constructor:
constructor() {
this.state = {
// put the array here
}
}
Or, I could make it a default prop:
Component.defaultProps = {
// put the array here
}
Which would then allow the component to access the array from either this.state or this.props.
However, I don't like either of these options, because both allow the array to be changed. If I put it in state, then the array can be changed from (almost) anywhere within the component, and if I put it in props, then the component could be passed an alternative array as a prop.
So: is there a way, in React JS, to define a static, unchangeable property on a component?

You shouldn't put things in the React state that aren't going to be reactive or manipulated in some way and setting it as a default prop is not a great solution either as that implies that sometimes the component will receive a prop of that name.
One solution could be to set up a function which simply returns this list:
getIdList() {
return [];
}
However, I think the better solution would be to just define it as a const above the React component. Seeing as this data will never be changed it doesn't really need to live within the component itself.
So, that would like something like the following:
const idArray = [];
class SomeReactComponent extends React.Component {

Since every module has it's own scope you can just put the array locally in the module of your component (above the component). If you don't export it it will be private
const texts = [
'Text for scenario 0',
'Text for scenario 1',
'Text for scenario 2',
]
export default class MyComponent extends Component {
...you can access texts as a const here
}

You can save variables in more than just state and props, neither of which you want want to use here, since this array is neither the state of the component or a property being passed to it.
You can, as others have said, store the array elsewhere in the file and simply reference it within your component.
You can store the array as a property of your component, and just reference it as this.myArray where needed:
constructor(props) {
super(props);
this.myArray = [1, 2, 3];
}
Or, if the array is not going to be changing from one instance of the component to the next, you can store it as a static property of the component and reference it as MyComponent.myArray where needed:
static myArray = [1, 2, 3];
The solution to just place it elsewhere in the file is a good one, especially if it is a const variable or if you may ever want to export and use it elsewhere in your project.

Related

ReactJS variables problems. It's updating variables that are not being touched

I'm working on a ReactJS app and i'm a new comer.
I have a Component like this
class Type extends React.Component {
constructor(props) {
super(props);
this.state = {
name : props.type.name,
description : props.type.description,
price : props.type.price,
imageList : props.type.images,
mode : 'view',
// i'm cloning the whole object
clone : props.type
};
}
handleDeleteImage(event) {
const imageId = event.target.getAttribute('data-imageId');
// get the current imageList of this Component
var imageList = this.state.imageList;
// checking the length of 2 image list before removing the
// targeted image
console.log(imageList.length) // displays 3
console.log(this.state.clone.images.length) // displays 3
// remove the targeted imageId
imageList.splice(imageId, 1);
// checking the length of 2 image list after removing the
// targeted image
console.log(imageList.length) // displays 2
console.log(this.state.clone.images.length) // displays 2
}
}
So what i'm doing here is i want to clone the object so when the user changes there mind and doesn't want to make changes anymore, they can hit the cancel button and everything is back to the state they were before (i have a function to handle this as well. I set the fields -name, description, price- to the values of the clone)
But as you can see, i didn't touched the image list in the clone at all still it got changed anyway.
Am i doing anything wrong here?
Thank you for any help.
Hey guys! So I realized that the concept I used in this service is not so efficient.
Like #Michael McQuade said, I should control the data in one flow only which is changing the data in the parent Component, not the child ones. I also reviewed the ReactJS Documentation and I can see why.
But with that being said. Let's say I'm working on a Component which has lots of Child-Component, does that mean I have to callback all the way up to the Parent Component to make changes in the Child one? And does that mean i must have multiple handlers in the Parent one that will be passed down to the Child that needs them?
I hope my question doesn't border you guys. Thanks!
You're using state and props together in a way I wouldn't recommend.
Instead of trying to make a copy of the props and storing it as state, make a stateless function and pass down a function which handles the deletion.
Here is an example:
class Child extends React.PureComponent {
render () {
return (<button onClick={this.props.handleBye}>{this.props.text}</button>)
}
}
class Parent extends React.PureComponent {
state = {
text: "Hello"
}
handler = () => {
this.setState({text: "bye"})
}
render() {
return (<Child text={this.state.text} handleBye={this.handler} />)
}ˆ
}
ReactDOM.render(<Parent />, document.body)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
This line will not clone an object, rather it will create a reference
clone : props.type
To clone you can use various techniques (depending on your need) one simple one would be
clone: Object.assign({}, props.type)
beware that this will only create a shallow copy of the object.
To create a deep copy you can use
clone: JSON.parse(JSON.stringify(props.type))
this is an easy technique but it is slow and will not copy dates correctly.
If you need fast and reliable deep clone you better search for something else that suits your needs (maybe a library like lodash).
this.state.clone is just a reference to the props.type object. So when you use splice() you change the contents of the array and therefore "mutate" props.type.
If you really want to clone the object do it like that:
this.state = {
clone: {...props.type} // create a new object and spread the props.type object properties
}
You can read more about the spread operator here

Can I make a certain function globally available inside a React application?

It is a common practice to pass in the form of a prop, from a root component A, to a subcomponent B, a function that will change the state of A. Like so:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
name: 'foo'
};
this.handleNameChange = this.handleNameChange.bind(this);
}
render() {
return (<NameChanger name={this.state.name} onNameChange={this.handleNameChange} />)
}
handleNameChange: function(newName) {
this.setState({
name: newName
});
}
}
Now as you can see NameChanger is one level down only so not a big issue there. But what if it had been down 3 or even 4 levels? We would have had to pass it down the chain of components and that bothers me big time. Is there a way to make a function globally available within the app?
I looked at Context (https://reactjs.org/docs/context.html) but I am not sure it is the right design choice for globally available functions. Or is it?
Thanks
In a typical React application, data is passed top-down (parent to
child) via props, but this can be cumbersome for certain types of
props (e.g. locale preference, UI theme) that are required by many
components within an application. Context provides a way to share
values like these between components without having to explicitly pass
a prop through every level of the tree.
https://reactjs.org/docs/context.html
Try using Redux or Mobx(very easy to start with) as state management library to solve this problem.

Best way to pass store to a child of connected component in React-Redux

What's the correct way to implement the below design?
<Parent with state>
<connected element with Store configuration dependant on parent>
<child of both, dependant on store/connected element>
</connected element>
</parent>
I'm not sure how much code to include and there's almost certainly more than I need so here's a snippet that I think explains what I'm trying to accomplish.
class SceneOne extends React.Component {
constructor (props) {
super(props);
this.state = {
opacity: 0,
script: sceneOneScript
};
}
render () {
return (
<ScriptReader script = {this.state.script}> //This is connected and creates a store from the script passed via state.
<Screen data-image="caves.png" data-opacity={this.state.opacity} >//This uses actual SceneOne.state.opacity which is updated to 1 after a delay in ComponentDidMount
<ConditionalTitle props = {this.props}/> //This needs the store.
</Screen>
</ScriptReader>
);
}
}
I'm really hoping that I don't need to connect <ConditionalTitle> because that feels like it breaks agnostic components principles. I'm also hoping that I don't need to install <ConditionalTitle> inside the definition of <ScriptReader> because I'm planning on reusing it and passing different children/scripts etc.
ie. there'll be a that has a <ScreenReader> child and it may not have a title, or may have elements that aren't required in <SceneOne>.
use a HOC inside the definition of the ScriptReader that composes all of the options

How to store the information in react component.

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

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