Why won't the updated variable values display? - javascript

I am setting initial values on a these variables. I change the values, and they do log correctly, however in the return function, they only display the original values. How do I make sure they show the correct updated values?
I declare my variables:
let teamBillableData = [];
let teamMemberBillableAmount = 0;
let teamMemberIndex = 0;
Then I change the values:
teamBillableData = parsedData.results;
teamMemberIndex = teamBillableData.findIndex(function (teamMember) {
return teamMember.user_name === teamMemberName;
})
teamMemberBillableAmount = teamBillableData[teamMemberIndex].billable_amount;
When I log the variables, they are correct:
console.log(teamMemberIndex); <---- Returns correct new value of 1
console.log(teamMemberBillableAmount); <---- Returns correct new value of 1,221.25
However, when I render the values in my return function in my React app, they render the initial values stull:
return (
<div>
<div>
<div>
<h5>{teamMemberIndex}</h5> <---- Returns old original value of 0
<h5>${teamMemberBillableAmount.toLocaleString()} </h5> <---- Returns old original value of 0
</div>
</div>
</div>
);
};
I assume it's rendering before the values are changed. But I do not know how to make it render AFTER they values are changed.

A React component only re-renders after either its state or its props change.
If you want your component to re-render when a variable changes, you have to add said variable to the component's state
If you're dealing with functional components, look into useState hook.

As told by Michael and Shubham you can write a functional component that triggers a rerender each time the state is updated using the useState hook.
An Example could be:
import React, { useState } from "react";
function Example() {
const [teamMemberBillableAmount, setTeamMemberBillableAmount] = useState(0);// initial state
const clickHandler = () => setTeamMemberBillableAmount(teamMemberBillableAmount + 1); // add 1 to the teamMemberBillableAmount state each time the button is clicked (a rerender will happen)
return (
<div>
<p>The team billable amount is:<strong style={{ color: "red" }}> teamMemberBillableAmount}</strong></p>
<button onClick={clickHandler}>Press me to add +1</button>
</div>
);
}
You can run and play with this code here

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.

Prop passed to child component does not change its value in child upon updating prop in parent

I have two functional components called Crucials and Timer, where Crucials is the parent and Timer is the Child.
Crucials has the user time data that needs to be passed down to the Timer for it to start the timer from the time provided by user.
My parent component is as follows:
export default function Crucials() {
const [Hdata, setHData] = useState(0);
const [Mdata, setMData] = useState(0);
let data = {
Hour: 0,
Min: 0,
Sec: 0
};
function timerStart(){
if(Hdata === 0 && Mdata === 0){
console.log("Minimum Timer Initiated");
setHData((minH)=> minH = 0);
setMData((minM)=> minM = 15);
}
data.Hour = 0;
data.Min = Mdata;
data.Secy = Hdata;
}
return (<div>
<div><Timer timerdata={data}/></div>
<Button variant="contained" color="primary" onClick={timerStart}> Start Timer</Button>
<form className={styles.HTM} onSubmit={timerStart}>
<Input type="number" onChange={e => setHData(e.target.value) } id="outlined-basic" placeholder="Hours(H)" ></Input>
<Input type="number" id="outlined-basic" onChange={e => setMData(e.target.value)} placeholder="Minutes(M)" ></Input>
</form>
</div>
)}
My child is as follows:
export default function Timer(timerdata) {
let Seconds = timerdata.Sec;
let Minutes = timerdata.Min;
let Hours = timerdata.Hour;
....
...
}
All I want to do is for me to be able to pass the data object to the child and for it to only update when timerStart is invoked via the Start Timer button.
However, what is happening is that the data object passed to the child only ever holds the initialization values (0,0,0) and invoking timerStart function, which changes the values within data, is not reflected in the Child.
Also, for some odd reason, just inputting values into the two <Input> fields causes child to update data, but it still holds the initial values rather than what the user input.
I'm probably doing something wrong, but I'm unable to figure it out. Any help is appreciated.
You need to make data a state value, currently:
your updates to your object won't persist between re-renders. Using setHData/setMData to update your state causes your component function to be called/executed again, redefining local variables such as the data object to the initial values
updating your data alone using data.Hour = 0; etc. won't cause your component/children components to re-render with the updated values. As a result, you need to use react's state setter function, setX to signal to react that your data object has changed and that your component needs to re-render using the new state values.
Instead, create a new state value that holds your data object, and use setData() to update it. This will cause your update to re-render your component + its children:
const {useState} = React;
const App = () => {
const [data, setData] = useState({hour:1,min:2});
const clickHandler = () => {
setData({hour: 10, min: 20}); // use input values instead of hard-coding values
};
return <div>
<Child data={data} />
<button onClick={clickHandler}>Update data</button>
</div>
}
const Child = (props) => <div>
<p>H: {props.data.hour}</p>
<p>M: {props.data.min}</p>
</div>;
ReactDOM.render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.0/umd/react-dom.production.min.js"></script>
Side note:
Your code setHData((minH)=> minH = 0); can be written as setHData(0);, and same with setMData(15). To update the state value, all you need to do is pass the new value to the state setter function. You don't need to pass an arrow function here, as your new value (0, and 15) doesn't rely on the previous state value.

How to Pass React form state from app to multiple components (functional)

I'm still fairly new to React, so I'm sorry if this is a repetitive post. I'm working with React to create a form that spreads across different pages. The idea is to have the type of form you'd receive from a job application. One that has multiple steps.
I have different components made for each step of the form. The first page is a home page. Imagine a button to take you to the create page. Then you see the Create page. There is a small form with an amount and name. When you click the next page, you'll see the Customize page. There you can edit your budget by priority.
Here in my poorly drawn picture, I have the app obviously, and a budget homepage to manage state across it's children. I figured I should do it this way because, well it's the only way I know how. The problems I have, are one: I don't know how to properly pass state throughout the components. Two: I don't even know if what I'm doing is correct. The React docs use forms in Class based components, which sort of helps, but I can't configure it to my problem.
Any help or suggestions are greatly appreciated. Also I want to show code from the Budget Page.
import React, { useState, useEffect, useRef } from "react";
import BudgetResultsPage from './BudgetResultsPage';
const [count, setState] = useState(0);
const handleStateCount = () => {
if(count>3) {count = 0;}
return setState(count + 1);
}
if(count === 0) {
console.log(`homepage state ${count}`)
return (
<div className={Styles.Calculator}>
<BudgetHomePage setState={count} handleStateCount={handleStateCount}/>
</div>
)
} else if(count===1) {
console.log(`budget create state ${count}`)
return (
<div className={Styles.Calculator}>
<CalculatorHeader />
<CreateBudget setState={count} handleStateCount={handleStateCount} setAmount={amount} handleAmount={handleAmount}/>
</div>
)}
There are obviously more imports for the other pages and more code passed in the component. This is just a snippet of the way I'm handling the components.
It's probably silly, but I'm creating a count in state, then when part of the form is submitted, the count goes up and it changes the page based on what the count is at. Works great for just one state, but I'm having problems with adding more state to it.
Thanks again!
I have written an example functional components illustrating some of your questions. I wrote this at home and only had notepad++ so might not compile if you copy paste. The comments will explain your questions
import React from 'react';
import Create_budget_page from './Create_budget_page.js';
const BudgetPage = props => {
// State 1: assigning one value to the state.
// State 2: assinging a list to the state.
// State 3: assinging a object to the state.
const [simple_state, set_simple_state] = useState(false);
const [list_state, set_list_state] = useState(["Hello","World","!"]);
const [object_state, set_object_state] = useState(
{
curmenu: "human_bios",
height: "196cm",
weight: "174lbs",
eye_colour: "blue",
hair_colour: "dirty_blonde"
}
);
// there are several possiblities, here's one with a list of objects
const [list_objects, set_list_objects] = useState(
[
{count: 69, wasClicked: false},
{count: 420, wasClicked: true},
// endless possibilities, the tricky part is properly correctly updating your states deep in the list
{integers: [1,5,2,3], keys: {isAlive: false, cur_menu: "config_menu"}}
]
);
// this function updates the state that stores an object
// arguments:
// new_values_object: the programmer passes in a object to the function with the values they want to update
// an example of calling this function in a child functional component:
// props.update_object_state({height: "165cm", weight: "143lbs", hair_colour: "black"});
function update_object_state(new_values_object){
set_object_state(prevState => {
const new_obj = prevState;
// loop through object keys and update the new_obj with the object passed in as a argument
for (var key in new_values_object){
new_obj[key] = new_values_object[key];
}
// returning the new_object will update the object_state
return new_obj;
})
}
// conditionally render based on state
switch(object_state["curmenu"]){
case "home_page":
return (
// pass in
// all props, 1 prop, state
<Create_budget_page {...props} parent_id={prop.parent_id} simple_state={simple_state} list_objects={list_objects}/>
) ;
break;
// pass in function
case "human_bios":
return (
<div className="error_page" onClick={() => {props.update_parent_state({error_occured: true})}}>This is an Error Page, click me to report diagnostics</div>
);
break;
// if none of cases are met default code executes
default:
// renders nothing
return null;
}
}
Which kind of problems are you experiencing? I assume that the part of code you left there is inside a component body (a function, as you are using functional way).
You should have no problems passing multiple props to child components even though you have to keep in mind other approaches (excesive number of props means something is wrong in general...)
Let me know if you have more questions.
EDIT:
Suppose you have that ResultsPage child component and in your parent component you do something like:
<ResultsPage someProp={someValue} someOtherProp={someOtherValue} />
Then in your child component you can manage those props like this:
const ResultsPage = (props) => {
// props is an object like this: { someProp: someValue, someOtherProp: someOtherValue}
... // and in the body of the function you can manipulate it however you want
}
Generally it is better to destructure your props variable in the param directly instead of always do props.someProp, etc. like this:
const ResultsPage = ({someProp, someOtherProp}) => { ... }
More info about destructure here

Using value returned from function in react component (auto-update)

I wish to use the value returned from this function:
const quantity = () => {
let cookies = document.cookie.split("?");
return cookies.length;
}
in my react component:
const cart_button = (
<Mfont>{quantity}</Mfont>
);
<Mfont> is a standard span element styled with styled-components;
console.log(cookies.length)
gives me a number based on its length as expected, but I can't figure out how to insert it to my component and update every time a cookie.length increases.
According to this answer, the most reliable way still in 2019 to detect changes in cookies is to check document.cookie on interval.
In React 16.8, we have useEffect hook that we could use together with useState hook to run a function on interval and cause the component to update when the value of document.cookie has been updated, like
import React, { useState, useEffect } from 'react';
const MyComponent = props => {
const [latestCookie, setLatestCookie] = useState(document.cookie);
useEffect(() => {
const detectCookieUpdate = document.cookie !== latestCookie && setLatestCookie(document.cookie);
const interval = window.setInterval(detectCookieUpdate, 1000);
return () => window.clearInterval(interval);
});
return (
<Mfont>{quantity()}</Mfont>
);
};
This way we run a function every 1 second that checks if the value of document.cookie is equal to the previous value. Since the value is always a string, it's safe to use strict comparison operator === and not do any checks on top of that. When the values don't match, we run setLatestCookie provided by the state hook, causing the component to render again, and therefore making use of quantity function that will run again.
Have you tried to pass length value as props to your component? Then you can assign that value to the component state. When state changes do whatever you want.

Categories

Resources