destructuring and using props in react components - javascript

Says I pass total_counts from parent to a a children component.
In my children component I have a render method
render() {
console.log(this.props.pagination.total_counts )
}
How do I use total_counts properly without error? My render method of children might render multiple times because pagination came through http call. If I do desctructring like below
const { total_counts } = this.props.pagination
render(){
return (
<div>{total_counts && <p>{total_counts.toString()}</p>}</div>
)
}
I still have to check total_counts is not undefined

If you are accessing total_counts from this.props.pagination, then the destructuring statement should be like this:
const { total_counts } = this.props.pagination;
This assumes that pagination would never be undefined. Otherwise, I suggest you check it first and fallback to some value if it does not exist;
// default value if this.props.pagination is undefined
let total_counts = 0;
// if this.props.pagination and total_counts property in it exist
// then assign total_counts variable
if (this.props.pagination && this.props.pagination.total_counts) {
total_counts = this.props.pagination.total_counts;
}

Related

Pass props and use ID again

I have a component that passes props to another component. Inside the component the props have been passed to, I declare the parameter set new variable and get the last item of the array like this:
var lastItem = passedProp[passedProp - 1] || null
My question is how do I pass this property back to another component to use in a global service I am using to run inside a function. From what I am aware props can only be passed down in React, not up? Please correct me if I am wrong. The end result I want to achieve is to use this property's ID in function I am using in global service.
read about lifting state up ...
https://reactjs.org/tutorial/tutorial.html#lifting-state-up
You can pass a function to the child and the child can pass the information through this function.
I let you an example that you can copy & paste to see how it works :)
import React from 'react';
function ChildComponent(props) {
const { data, passElementFromChild } = props;
const lastElement = data[data.length - 1] || null;
setTimeout(() => {
passElementFromChild('this string is what the parent is gonna get');
}, 300);
return (
<div>Last element of the array is: {lastElement}</div>
);
}
function Question17() {
const data = ['firstElement', 'middleElement', 'lastElement']
const passElementFromChild = (infoFromChild) => {
console.log("infoFromChild: ", infoFromChild);
}
return (
<ChildComponent data={data} passElementFromChild={passElementFromChild} />
);
}
export default Question17;

Change a prop value inside functional component can cause problems in react component?

If I receive a prop and change it in my functional component, can it create a problem? Or it's ok to change it inside the component?
e.g
const MyComponent = ({ foo }) => {
// is this bad?
foo = someCondition ? bar : foo
return (...)
}
I know that I could create another variable and store the values, but I would like to know if changing the prop it self won't cause any problem because it's a functional component.
No, it shouldn't create any problems. As with regular functions the arguments passed are their own variables in the function scope and don't mutate the original value passed to the function.
function something(value) {
value = 'nothing';
}
var anything = 0;
something(anything);
// Anything should still be 0;
console.log(anything);
But I would suggest to not mutate your variables.
If foo in your example is passed from the parrent, and the parrent keeps it in its state, then you would also need to pass setFoo as a paramater to your component and use that to update it properly.
function Parrent(){
let [foo, setFoo] = useState('foo');
return <Child foo={foo} setFoo={setFoo}/>
}
As for changing the props directly, you can if they are arrays or objects.
Props in the React are just read-only variables. You should change the props values by the parent component
I avoid changing the prop.
But I created a simple example and changing the prop in the children do not affected the value in the parent component.
https://codesandbox.io/s/objective-cdn-cq55d
I tested it with several render. Thats why I added an input. Typing in it makes the component rerender.
const MyComponent = ({ foo }) => {
// Not valid
foo = someCondition ? bar : foo
return (...)
}
There are two kinds of data in React,
a)Props(immutable data)
b)State(mutable data)
you are not supposed to change the immutable data(there are some ways to do it, but not recommended). what you should do is, (you can't assign a callback and change from here, i'll explain later why)
if you want to just use just the value inside this component
const baz = foo === condition ? bar : foo
or render something based on foo meets some condition
return (
<div>
{foo === somecondition ? <A /> : <B />}
</div>
)
Or you want to actually change it,
coming from a global state like redux or mobx,
u should change it from the reducers in case of redux or
#action decorated functions in mobx.
if it's a local state which passed down to the child component,
u can set a call back and assign to a click handler in which the case it is feasible
handleClick = () => {
this.setState(prevState => ({
...prevState,
foo: someCondition ? bar : foo,
}))
}
render () {
const { handleClick } = this
return <ChildComponent {...{ handleClick }} />
}
Like said before u can't change the passed down local state from render of the child(or render of any component[actually u can, but may end up in infinite loops: each time a state change happens, the component will re render, so the loop(pure components excluded eg: shouldComponentUpdate() hook which validates an unrelated condition)])
in such cases what u should do is to make the child component also a stateful component and change the parent props with a callback
class Child extends Component {
//set once
componentWillMount() {
if (condition) callback
}
//whenever there is change
componentWillReceiveProps(nextProps) {
if (condition) callback
}
//use correct lifecycle method which meets your requirement..
}

Why isn't the mobx #computed value?

Simple: the computed value isn't updating when the observable it references changes.
import {observable,computed,action} from 'mobx';
export default class anObject {
// THESE WRITTEN CHARACTERISTICS ARE MANDATORY
#observable attributes = {}; // {attribute : [values]}
#observable attributeOrder = {}; // {attribute: order-index}
#observable attributeToggle = {}; // {attribute : bool}
#computed get orderedAttributeKeys() {
const orderedAttributeKeys = [];
Object.entries(this.attributeOrder).forEach(
([attrName, index]) => orderedAttributeKeys[index] = attrName
);
return orderedAttributeKeys;
};
changeAttribute = (existingAttr, newAttr) => {
this.attributes[newAttr] = this.attributes[existingAttr].slice(0);
delete this.attributes[existingAttr];
this.attributeOrder[newAttr] = this.attributeOrder[existingAttr];
delete this.attributeOrder[existingAttr];
this.attributeToggle[newAttr] = this.attributeToggle[existingAttr];
delete this.attributeToggle[existingAttr];
console.log(this.orderedAttributeKeys)
};
}
After calling changeAttribute, this.orderedAttributeKeys does not return a new value. The node appears unchanged.
However, if I remove the #computed and make it a normal (non-getter) function, then for some reason this.orderedAttributeKeys does display the new values. Why is this?
EDIT: ADDED MORE INFORMATION
It updates judging by logs and debugging tools, but doesn't render on the screen (the below component has this code, but does NOT re-render). Why?
{/* ATTRIBUTES */}
<div>
<h5>Attributes</h5>
{
this.props.appStore.repo.canvas.pointerToObjectAboveInCanvas.orderedAttributeKeys.map((attr) => { return <Attribute node={node} attribute={attr} key={attr}/>})
}
</div>
pointerToObjectAboveInCanvas is a variable. It's been set to point to the object above.
The changeAttribute function in anObject is called in this pattern. It starts in the Attribute component with this method
handleAttrKeyChange = async (existingKey, newKey) => {
await this.canvas.updateNodeAttrKey(this.props.node, existingKey, newKey);
this.setState({attributeEdit: false}); // The Attribute component re-renders (we change from an Input holding the attribute prop, to a div. But the above component which calls Attribute doesn't re-render, so the attribute prop is the same
};
which calls this method in another object (this.canvas)
updateNodeAttrKey = (node, existingKey, newKey) => {
if (existingKey === newKey) { return { success: true } }
else if (newKey === "") { return { success: false, errors: [{msg: "If you'd like to delete this attribute, click on the red cross to the right!"}] } }
node.changeAttribute(existingKey, newKey);
return { success: true }
};
Why isn't the component that holds Attribute re-rendering? It's calling orderedAttributeKeys!!! Or am I asking the wrong question, and something else is the issue...
An interesting fact is this same set of calls happens for changing the attributeValue (attribute is the key in anObject's observable dictionary, attributeValue is the value), BUT it shows up (because the Attribute component re-renders and it pulls directly from the node's attribute dictionary to extract the values. Again, this is the issue, an attribute key changes but the component outside it doesn't re-render so the attribute prop doesn't change?!!!
It is because you have decorated changeAttribute with the #action decorator.
This means that all observable mutations within that function occur in a single transaction - e.g. after the console log.
If you remove the #action decorator you should see that those observables get updated on the line they are called and your console log should be as you expect it.
Further reading:
https://mobx.js.org/refguide/action.html
https://mobx.js.org/refguide/transaction.html
Try to simplify your code:
#computed
get orderedAttributeKeys() {
const orderedAttributeKeys = [];
Object.entries(this.attributeOrder).forEach(
([attrName, index]) => orderedAttributeKeys[index] = this.attributes[attrName])
);
return orderedAttributeKeys;
};
#action.bound
changeAttribute(existingAttr, newAttr) {
// ...
};
Also rename your Store name, Object is reserved export default class StoreName

Is there a way to stagger React Hooks?

I'm trying to avoid using conditional statements with React Hooks, but I've reached a point where I may need some advanced techniques. I have here 3 hooks that depend on each other. Each hook returns a tuple [bool, object] that the next hook requires to execute.
Internally, each hook is an asynchronous operation and I don't have access to modify them.
const [loadingA, resultA] = useHook1();
const [loadingB, resultB] = useHook2(resultA.prop1); // Error!
const [loadingC, resultC] = useHook3(resultB.prop2); // Error!
if (loadingA || loadingB || loadingC) {
return <span>loading...</span>;
}
// All results are required for this component.
return <MyComponent x={resultA.x} y={resultB.y} z={resultC.z} />;
The above breaks because useHook2 and useHook3 require the arguments to be defined. Unfortunately, I can't use a condition like this:
// this is not allowed
if (!loadingA) {
const [loadingB, resultB] = useHook2(resultA.prop1);
}
Does anyone have any tips on how to stagger hooks such that they execute based on the result of a previous asynchronous hook?
Return some default value in hook2 and hook3 if the parameter is undefined. If you can't modify hook2 or hook3 directly, you can always wrap them in another hook (useHook2Wrapper, useHook3Wrapper) and do the undefined value validation.
const [loadingA, resultA] = useHook1();
const [loadingB, resultB] = useHook2(resultA && resultA.prop1);
const [loadingC, resultC] = useHook3(resultB && resultB.prop2);
const useHook2 = (someProp) => {
if (!someProp) {
return [false, undefined];
}
// rest of the hook logic
}

Method Expression is not of function type (equals on a state property/value) - React JS

I'm currently trying to do conditional statements which allow me to display the divs according to the User's role. First I call for the role and set it to the state value.
The sets are fine as I can view in the dev tools console. However when I try to do the following conditional check on a constant which is a string:
props = {
subRole = ''
}
{(!this.state.AdminRole && subRole.toString() === READ_ONLY (
//div goes here
))}
Then I get:
Object(...) is not a function
on the subRole.toString() === READ_ONLY check and Webstorm is telling:
method expression is not of function type
You have declared subRole incorrectly. In object notation we use : for keys.
Please do the following changes first:
props = {
subRole : ''
}
If you want to initialize default props:
// for example your component is App.js
App.defaultProps = {
roles : {
subRole: 'c'
}
};
Later you can access this in your component like:
this.props.roles.subRole
This is the recommended way but usually we don't use default props, instead we are more concerned with the component's default state.
READ_ONLY is being called as a method which is why you get the error. Add && before rendering element:
{(!this.state.AdminRole && subRole.toString() === READ_ONLY && (
//div goes here
))}
Also, Fix =
props = {
subRole : ''
}

Categories

Resources