Pass props and use ID again - javascript

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;

Related

React use effect causing infinite loop

Am new to react and am creating a custom select component where its supposed to set an array selected state and also trigger an onchange event and pass it to the parent with the selected items and also get initial value as prop and set some data.
let firstTime = true;
const CustomSelect = (props)=>{
const [selected, setSelected] = useState([]);
const onSelectedHandler = (event)=>{
// remove if already included in the selected items remove
//otherwise add
setSelected((prev)=>{
if (selected.includes(value)) {
values = prevState.filter(item => item !== event.target.value);
}else {
values = [...prevState, event.target.value];
}
return values;
})
// tried calling props.onSelection(selected) but its not latest value
}
//watch when the value of selected is updated and pass onchange to parent
//with the newest value
useEffect(()=>{
if(!firstTime && props.onSelection){
props.onSelection(selected);
}
firstTime = false;
},[selected])
return (<select onChange={onSelectedHandler}>
<option value="1"></option>
</select>);
};
Am using it on a parent like
const ParentComponent = ()=>{
const onSelectionHandler = (val)=>{
//do stuff with the value passed
}
return (
<CustomSelect initialValue={[1,2]} onSelection={onSelectionHandler} />
);
}
export default ParentComponent
The above works well but now the issue comes in when i want to set the initialValue passed from the parent onto the customSelect by updating the selected state. I have added the followin on the CustomSelect, but it causes an infinite loop
const {initialValue} = props
useEffect(()=>{
//check if the value of initialValue is an array
//and other checks
setSelected(initialValue)
},[initialValue]);
I understand that i could have passed the initialValue in the useState but i would like to do a couple of checks before setting the selected state.
How can i resolve this, am still new to react.
In your //do stuff with the value passed you are most likely update the states of your component parent component and it causes to rerender parent component. When passing the prop initialValue={[1,2]} creates a new instance of [1,2] array on each render and causes the infinite render on useEffect. In order to solve this, you can move the initialValue prop to somewhere else as const value like this:
const INITIAL_VALUE_PROP = [1,2];
const ParentComponent = ()=>{
const onSelectionHandler = (val)=>{
//do stuff with the value passed
}
return (
<CustomSelect initialValue={INITIAL_VALUE_PROP} onSelection={onSelectionHandler}
/>
);
}
export default ParentComponent
Okay, so the reason this is happening is that you're passing the initialValue prop as an array, and since this is a reference value in JavaScript, it means that each time it's passed(updated), it's passed with a different reference value/address, and so the effect will continue to re-run infinitely. One way to solve this is to use React.useMemo, documentation here to store/preserve the reference value of the array passed, not to cause unnecessary side effects running.

React.js State updating correctly only once, then failing [duplicate]

This question already has answers here:
Using a Set data structure in React's state
(2 answers)
Closed 1 year ago.
I have a parent and a child component. There are 3 props the parent provides out of which 1 is not updating correctly.
Following is the parent component. The prop in question is selectedFilters (which is an object where keys are mapped to sets) and the relevant update function is filterChanged (this is passed to the child)
import filters from "../../data/filters"; //JSON data
const Block = (props) => {
const [selectedFilters, setSelectedFilters] = useState({versions: new Set(), languages: new Set()});
console.log(selectedFilters);
const filterChanged = useCallback((filter_key, filter_id) => {
setSelectedFilters((sf) => {
const newSFSet = sf[filter_key]; //new Set(sf[filter_key]);
if (newSFSet.has(filter_id)) {
newSFSet.delete(filter_id);
} else {
newSFSet.add(filter_id);
}
const newSF = { ...sf, [filter_key]: new Set(newSFSet) };
return newSF;
});
}, []);
return (
<FilterGroup
filters={filters}
selectedFilters={selectedFilters}
onFilterClick={filterChanged}
></FilterGroup>
);
};
export default Block;
The following is the child component: (Please note that while the Filter component runs the filterChanged function, I think it is irrelevant to the error)
import Filter from "./Filter/Filter";
const FilterGroup = (props) => {
const { filters, selectedFilters, onFilterClick } = props;
console.log(selectedFilters);
const filter_view = (
<Container className={styles.container}>
{Object.keys(filters).map((filter_key) => {
const filter_obj = filters[filter_key];
return (
<Filter
key={filter_obj.id}
filter_key={filter_key}
filter_obj={filter_obj}
selectedFilterSet={selectedFilters[filter_key]}
onFilterClick={onFilterClick}
/>
);
})}
</Container>
);
return filter_view;
};
export default FilterGroup;
When running the application, I find that the selectedFilters updates correctly only once. After that, it only changes temporarily in the main Block.tsx, but eventually goes back to the first updated value. Also, FilterGroup.tsx only receives the first update. After that, it never receives any further updated values.
Here are the logs:
After some experimentation, it is clear that the problem originates from the filterChanged function. But I cannot seem to figure out why the second update is temporary AND does not get passed on to the child.
Any ideas? Thanks in advance.
(If any other info is required, pls do mention it)
I don't think you actually want your filterChanged function to be wrapped with useCallback, especially with an empty deps array. with the empty deps array, I believe useCallback will fire once on initial render, and memoize the result. You may be able to add filter_key and filter_id to the dependency array, but useCallback tends to actually slow simple functions down, instead of adding any real performance benefit, so you may just want to get rid of the useCallback completely and switch filterChanged to a regular arrow function.

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

destructuring and using props in react components

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

Categories

Resources