Reassignment of variables react - reactjs

Playing around with React, what is the logic behind the reassignment of variables not changing. For example when creating a var didClick assigning it to false and then changing the var didClick to true when we click on the display button my component still does not list out all the task text.
function App() {
/*var didClick = false*/
var [didClick,setdidClick] = useState(false);
const [tasks,setTasks] = useState([
{
id: 1,
text:'Doctors Appointment',
day:'Feb 5th at 2:30pm',
},
{
id: 2,
text:'Cat Appointment',
day:'Feb 5th at 4:30pm',
},
{
id: 3,
text:'Dog Appointment',
day:'Feb 5th at 7:30pm',
}
])
const functiontoClick = () => {
/*didClick = true*/
setdidClick(true)
}
return (
<div>
<button onClick={()=> functiontoClick()}>Display</button>
<div>
{didClick ? tasks.map((task) => (
<li key={task.id}> {task.text} </li>
)) : null }
</div>
</div>
);
}
export default App;

using useState will ensures that the component has been updated and calls for re-rendering of the component in react.
but using normal declaration using var, const and let will not update or re-render the component.
you can see what happened if you add console.log just before the return.
first case using useState:
var [didClick,setdidClick] = useState(false);
...
const functiontoClick = () => {
/*didClick = true*/
setdidClick(true)
}
console.log({ didClick })
return (
...
the console will return false at first render and after clicking will console true and render the component.
second case using var:
var didClick = false;
...
const functiontoClick = () => {
didClick = true
}
console.log({ didClick })
return (
...
the console will return false at first render and after clicking will not console anything because it's not rendering the component again

As pointed out in the comments it is unclear what you are asking for: but for your example, if you wanna toggle the display you can use the following code:
const [didClick,setdidClick] = useState(false);
<button onClick={()=> useState(!didClick)}>Display/Hide</button>
If your code at the moment you are not using the functionality being provided by useState:

Related

how can conditional rendering reflect the state from a list of Boolean using hook?

The Goal:
My React Native App shows a list of <Button /> based on the value from a list of Object someData. Once a user press a <Button />, the App should shows the the text that is associated with this <Button />. I am trying to achieve this using conditional rendering.
The Action:
So first, I use useEffect to load a list of Boolean to showItems. showItems and someData will have the same index so I can easily indicate whether a particular text associated with <Button /> should be displayed on the App using the index.
The Error:
The conditional rendering does not reflect the latest state of showItems.
The Code:
Here is my code example
import {someData} from '../data/data.js';
const App = () => {
const [showItems, setShowItems] = useState([]);
useEffect(() => {
const arr = [];
someData.map(obj => {
arr.push(false);
});
setShowItems(arr);
}, []);
const handlePressed = index => {
showItems[index] = true;
setShowItems(showItems);
//The list is changed.
//but the conditional rendering does not show the latest state
console.log(showItems);
};
return (
<View>
{someData.map((obj, index) => {
return (
<>
<Button
title={obj.title}
onPress={() => {
handlePressed(index);
}}
/>
{showItems[index] && <Text>{obj.item}</Text>}
</>
);
})}
</View>
);
};
This is because react is not identifying that your array has changed. Basically react will assign a reference to the array when you define it. But although you are changing the values inside the array, this reference won't be changed. Because of that component won't be re rendered.
And furthermore, you have to pass the key prop to the mapped button to get the best out of react, without re-rendering the whole button list. I just used trimmed string of your obj.title as the key. If you have any sort of unique id, you can use that in there.
So you have to notify react, that the array has updated.
import { someData } from "../data/data.js";
const App = () => {
const [showItems, setShowItems] = useState([]);
useEffect(() => {
const arr = [];
someData.map((obj) => {
arr.push(false);
});
setShowItems(arr);
}, []);
const handlePressed = (index) => {
setShowItems((prevState) => {
prevState[index] = true;
return [...prevState];
});
};
return (
<View>
{someData.map((obj, index) => {
return (
<>
<Button
key={obj.title.trim()}
title={obj.title}
onPress={() => {
handlePressed(index);
}}
/>
{showItems[index] && <Text>{obj.item}</Text>}
</>
);
})}
</View>
);
};
showItems[index] = true;
setShowItems(showItems);
React is designed with the assumption that state is immutable. When you call setShowItems, react does a === between the old state and the new, and sees that they are the same array. Therefore, it concludes that nothing has changed, and it does not rerender.
Instead of mutating the existing array, you need to make a new array:
const handlePressed = index => {
setShowItems(prev => {
const newState = [...prev];
newState[index] = true;
return newState;
});
}

Using React hook form getValues() within useEffect return function, returns {}

I'm using react-hook-form library with a multi-step-form
I tried getValues() in useEffect to update a state while changing tab ( without submit ) and it returned {}
useEffect(() => {
return () => {
const values = getValues();
setCount(values.count);
};
}, []);
It worked in next js dev, but returns {} in production
codesandbox Link : https://codesandbox.io/s/quirky-colden-tc5ft?file=/src/App.js
Details:
The form requirement is to switch between tabs and change different parameters
and finally display results in a results tab. user can toggle between any tab and check back result tab anytime.
Implementation Example :
I used context provider and custom hook to wrap setting data state.
const SomeContext = createContext();
const useSome = () => {
return useContext(SomeContext);
};
const SomeProvider = ({ children }) => {
const [count, setCount] = useState(0);
const values = {
setCount,
count
};
return <SomeContext.Provider value={values}>{children}</SomeContext.Provider>;
};
Wrote form component like this ( each tab is a form ) and wrote the logic to update state upon componentWillUnmount.
as i found it working in next dev, i deployed it
const FormComponent = () => {
const { count, setCount } = useSome();
const { register, getValues } = useForm({
defaultValues: { count }
});
useEffect(() => {
return () => {
const values = getValues(); // returns {} in production
setCount(values.count);
};
}, []);
return (
<form>
<input type="number" name={count} ref={register} />
</form>
);
};
const DisplayComponent = () => {
const { count } = useSome();
return <div>{count}</div>;
};
Finally a tab switching component & tab switch logic within ( simplified below )
const App = () => {
const [edit, setEdit] = useState(true);
return (
<SomeProvider>
<div
onClick={() => {
setEdit(!edit);
}}
>
Click to {edit ? "Display" : "Edit"}
</div>
{edit ? <FormComponent /> : <DisplayComponent />}
</SomeProvider>
);
}

Scroll to element on page load with React Hooks

I'm trying to create a functional component that fetches data from an API and renders it to a list. After the data is fetched and rendered I want to check if the URL id and list item is equal, if they are then the list item should be scrolled into view.
Below is my code:
import React, { Fragment, useState, useEffect, useRef } from "react";
export default function ListComponent(props) {
const scrollTarget = useRef();
const [items, setItems] = useState([]);
const [scrollTargetItemId, setScrollTargetItemId] = useState("");
useEffect(() => {
const fetchData = async () => {
let response = await fetch("someurl").then((res) => res.json());
setItems(response);
};
fetchData();
if (props.targetId) {
setScrollTargetItemId(props.targetId)
}
if (scrollTarget.current) {
window.scrollTo(0, scrollTarget.current.offsetTop)
}
}, [props]);
let itemsToRender = [];
itemsToRender = reports.map((report) => {
return (
<li
key={report._id}
ref={item._id === scrollTargetItemId ? scrollTarget : null}
>
{item.payload}
</li>
);
});
return (
<Fragment>
<ul>{itemsToRender}</ul>
</Fragment>
);
}
My problem here is that scrollTarget.current is always undefined. Please advice what I'm doing wrong. Thanks in advance.
Using useCallback, as #yagiro suggested, did the trick!
My code ended up like this:
const scroll = useCallback(node => {
if (node !== null) {
window.scrollTo({
top: node.getBoundingClientRect().top,
behavior: "smooth"
})
}
}, []);
And then I just conditionally set the ref={scroll} on the node that you want to scroll to.
That is because when a reference is changed, it does not cause a re-render.
From React's docs: https://reactjs.org/docs/hooks-reference.html#useref
Keep in mind that useRef doesn’t notify you when its content changes. Mutating the .current property doesn’t cause a re-render. If you want to run some code when React attaches or detaches a ref to a DOM node, you may want to use a callback ref instead.
constructor(props) {
thi.modal = React.createRef();
}
handleSwitch() {
// debugger
this.setState({ errors: [] }, function () {
this.modal.current.openModal('signup') // it will call function of child component of Modal
});
// debugger
}
return(
<>
<button className="login-button" onClick={this.handleSwitch}>Log in with email</button>
<Modal ref={this.modal} />
</>
)
React Hooks will delay the scrolling until the page is ready:
useEffect(() => {
const element = document.getElementById('id')
if (element)
element.scrollIntoView({ behavior: 'smooth' })
}, [])
If the element is dynamic and based on a variable, add them to the Effect hook:
const [variable, setVariable] = useState()
const id = 'id'
useEffect(() => {
const element = document.getElementById(id)
if (element)
element.scrollIntoView({ behavior: 'smooth' })
}, [variable])

React why the state is not updating when calling a function to initialize it?

Playing with React those days. I know that calling setState in async. But setting an initial value like that :
const [data, setData] = useState(mapData(props.data))
should'nt it be updated directly ?
Bellow a codesandbox to illustrate my current issue and here the code :
import React, { useState } from "react";
const data = [{ id: "LION", label: "Lion" }, { id: "MOUSE", label: "Mouse" }];
const mapData = updatedData => {
const mappedData = {};
updatedData.forEach(element => (mappedData[element.id] = element));
return mappedData;
};
const ChildComponent = ({ dataProp }) => {
const [mappedData, setMappedData] = useState(mapData(dataProp));
console.log("** Render Child Component **");
return Object.values(mappedData).map(element => (
<span key={element.id}>{element.label}</span>
));
};
export default function App() {
const [loadedData, setLoadedData] = useState(data);
const [filter, setFilter] = useState("");
const filterData = () => {
return loadedData.filter(element =>
filter ? element.id === filter : true
);
};
//loaded comes from a useEffect http call but for easier understanding I removed it
return (
<div className="App">
<button onClick={() => setFilter("LION")}>change filter state</button>
<ChildComponent dataProp={filterData()} />
</div>
);
}
So in my understanding, when I click on the button I call setFilter so App should rerender and so ChildComponent with the new filtered data.
I could see it is re-rendering and mapData(updatedData) returns the correct filtered data BUT ChildComponent keeps the old state data.
Why is that ? Also for some reason it's rerendering two times ?
I know that I could make use of useEffect(() => setMappedData(mapData(dataProp)), [dataProp]) but I would like to understand what's happening here.
EDIT: I simplified a lot the code, but mappedData in ChildComponent must be in the state because it is updated at some point by users actions in my real use case
https://codesandbox.io/s/beautiful-mestorf-kpe8c?file=/src/App.js
The useState hook gets its argument on the very first initialization. So when the function is called again, the hook yields always the original set.
By the way, you do not need a state there:
const ChildComponent = ({ dataProp }) => {
//const [mappedData, setMappedData] = useState(mapData(dataProp));
const mappedData = mapData(dataProp);
console.log("** Render Child Component **");
return Object.values(mappedData).map(element => (
<span key={element.id}>{element.label}</span>
));
};
EDIT: this is a modified version in order to keep the useState you said to need. I don't like this code so much, though! :(
const ChildComponent = ({ dataProp }) => {
const [mappedData, setMappedData] = useState(mapData(dataProp));
let actualMappedData = mappedData;
useMemo(() => {
actualMappedData =mapData(dataProp);
},
[dataProp]
)
console.log("** Render Child Component **");
return Object.values(actualMappedData).map(element => (
<span key={element.id}>{element.label}</span>
));
};
Your child component is storing the mappedData in state but it never get changed.
you could just use a regular variable instead of using state here:
const ChildComponent = ({ dataProp }) => {
const mappedData = mapData(dataProp);
return Object.values(mappedData).map(element => (
<span key={element.id}>{element.label}</span>
));
};

changes to state issued from custom hook not causing re-render even though added to useEffect

I have a custom hook that keeps a list of toggle states and while I'm seeing the internal state aligning with my expectations, I'm wondering why a component that listens to changes on the state kept by this hook isn't re-rendering on change. The code is as follows
const useToggle = () => {
const reducer = (state, action) => ({...state, ...action});
const [toggled, dispatch] = useReducer(reducer, {});
const setToggle = i => {
let newVal;
if (toggled[i] == null) {
newVal = true;
} else {
newVal = !toggled[i];
}
dispatch({...toggled, [i]: newVal});
console.log('updated toggled state ...', toggled);
};
return {toggled, setToggle};
};
const Boxes = () => {
const {setToggle} = useToggle()
return Array.from({length: 8}, el => null).map((el,i) =>
<input type="checkbox" onClick={() => setToggle(i)}/>)
}
function App() {
const {toggled} = useToggle()
const memoized = useMemo(() => toggled, [toggled])
useEffect(() => {
console.log('toggled state is >>>', toggled) // am not seeing this on console after changes to toggled
}, [toggled])
return (
<div className="App">
<Boxes />
</div>
);
}
It's because you are using useToggle twice.
once in the App
another one in the Boxes.
When you dispatch the action in Boxes, it's updating the toggled instance for Boxes (which is not retrieved in it).
Think of your custom hook like how you use useState. When you use useState, each component gets its own state. Same goes for the custom hook.
So there are a few ways you can address the issue.
Pass the setToggle from App to Boxes via prop-drilling
Use Context API (or Redux or other statement management library to pass
setToggle instance in the App component down)
Here is an example of prop-drilling.
You can follow along
const Boxes = ({ setToggle }) => {
// const { setToggle } = useToggle();
return Array.from({ length: 8 }, el => null).map((el, i) => (
<input key={i} type="checkbox" onClick={() => setToggle(i)} />
));
};
function App() {
const { toggled, setToggle } = useToggle();
useEffect(() => {
console.log("toggled state is >>>", toggled); // am not seeing this on console after changes to toggled
}, [toggled]);
return (
<div className="App">
<Boxes setToggle={setToggle} />
</div>
);
}
Note: that I added key props in Boxes using the index i(and it is a bad practice by the way)
You can see that it's now working as you'd expect.

Resources