What would be the implementation for the code in Vanilla JS that allows us to declare and update state like the way useState does so in React:
const [x, setX] = useState(12);
setX(14);
console.log(x); // 14
This question is strictly get better at JS. Naively it would make sense to go with:
// Solution 1
function update(value, newValue) {
value = newValue;
return value;
}
function state(value) {
return [ value, update ];
}
let [value, setValue] = state(12)
value = setValue(value, 14)
console.log(value); // 14
// Solution 2
class State {
constructor(value) {
this.value = value;
}
update(newValue) {
this.value = newValue;
}
}
const x = new State(12);
x.update(14);
console.log(x.value); // 14
But I don't understand how the array [x, setX] has a callback (setX) that can affect x when declared with a const? I hope that makes sense.
I wanted to learn how to accomplish this as well. I found how to do it here. I refactored the code to use arrow functions instead, which can make the code snippet harder to read & understand. If it's the case, head to the resources shared in the link above.
This is the implementation:
const useState = (defaultValue) => {
// 👆 We create a function useState with a default value
let value = defaultValue;
// 👆 We create a local variable value = defaultValue
const getValue = () => value
// 👇 We create a function to set the value with parameter newValue
const setValue = newValue => value = newValue // 👈 We change the value for newValue
return [getValue, setValue]; // 👈 We return an array with the value and the function
}
const [counter, setCounter] = useState(0);
// 👆 We destructure the array as a return of the useState function into two value
console.log(counter()); // 👈 returns 0 which it's the value of counter()
I added the comments for easier understanding. This is the implementation withouth the comments:
const useState = (defaultValue) => {
let value = defaultValue;
const getValue = () => value
const setValue = newValue => value = newValue
return [getValue, setValue];
}
const [counter, setCounter] = useState(0);
console.log(counter());
For better reading & understanding, I have included the snippet using regular functions:
function useState(defaultValue) {
let value = defaultValue
function getValue() {
return value
}
function setValue(newValue) {
value = newValue
}
return [getValue, setValue];
}
const [counter, setCounter] = useState(0);
There is something very important you are missing - all react hooks use something "backing" them which allows you to provide what are effectively instance variables when you don't have an instance, you only have a function.
This thing in React is called a fiber and it effectively represents the lifecycle of a React component - it's not tied to the function itself, per se, it's tied to the component react is rendering (and re-rendering). Which is why you can have one functional component declaration, render that same function multiple times, and each of those will be able to maintain their own state - the state isn't part of the function, the state is part of the React fiber.
But I don't understand how the array [x, setX] has a callback (setX)
that can affect x when declared with a const?
You aren't simply mutating the value of x when you call setX, what you are doing is telling React to re-render the component (fiber) with a new value for x.
EDIT:
A tremendously simplistic example, where the function itself is used as the backing instance of state (which is not the case in React) could look something like this:
// this line is example only so we can access the stateSetter external to the function
let stateSetter;
const states = new Map();
const useState = (value,context) => {
const dispatch = v => {
const currentState = states.get(context.callee);
currentState[0] = typeof v === 'function' ? v(currentState[0]) : v
// we re-call the function with the same arguments it was originally called with - "re-rendering it" of sorts...
context.callee.call(context);
}
const current = states.get(context.callee) || [value,dispatch];
states.set(context.callee,current);
return current;
}
const MyFunction = function(value) {
const [state,setState] = useState(value, arguments)
stateSetter = setState;
console.log('current value of state is: ',state)
}
MyFunction(10);
MyFunction(20); // state hasn't changed
stateSetter('new state'); // state has been updated!
A simple solution to mock the useState() using a constructor. This may not be the best solution as the constructor returns a copy of the function every time but achieves the problem in question.
function Hook(){
return function (initialState){
this.state = initialState;
return [
this.state,
function(newState){
this.state = newState;
}
];
}
}
const useState = new Hook();
Now, destructure the useState() which is an instace of Hook()
const [state, setState] = useState(0);
console.log(state); // 0
setState({x:20});
console.log(state); // { x: 20 }
setState({x:30});
console.log(state); // { x: 30 }
1.- Destructuring for values returned by a function.
We apply it to destructure two values of an array
returned in a function.
The first value will return the current data of a variable and the second one will have the change function for said value.
// Main function useState (similar to react Hook)
function useState(value){
// Using first func to simulate initial value
const getValue = () => {
return value;
};
// The second function is to return the new value
const updateValue = (newValue) => {
// console.log(`Value 1 is now: ${newValue}`);
return value = newValue;
};
// Returning results in array
return [getValue, updateValue];
}
// Without destructuring
const initialValue = useState(3);
const firstValue = initialValue[0];
const secondValue = initialValue[1];
// Set new data
console.log("Initial State", firstValue()); // 3
console.log("Final", secondValue(firstValue() + 5)); // 8
console.log("===========================");
// With destructuring
const [counter, setCounter] = useState(0);
console.log("Initial State", counter()); // 0
setCounter(counter() + 20);
console.log("Final", counter());
Related
I am getting different behaviour depending on whether I am using a boolvalue on with useState, or whether I am using a bool value inside an object with useState.
This first bit of code will show the hidden text when the button is pressed. It uses contextMenuIsOpen which is a bool directly on the state, to control the visibility of the hidden text.
const Parent = () => {
const [contextMenuState, setContextMenuState] = useState({ isOpen: false, x: 0, y: 0, clipboard:null });
const [contextMenuIsOpen, setContextMenuIsOpen] = useState(false);
const openChild = ()=>{
setContextMenuIsOpen(true);
}
return <div><h1>Hello</h1>
<button onClick={openChild}>Open Child</button>
{contextMenuIsOpen &&
<h1>hidden</h1> }
</div>
}
export default Parent;
This next bit of code uses a property on an object which is on the state. It doesn't show the hidden text when I do it this way.
const Parent = () => {
const [contextMenuState, setContextMenuState] = useState({ isOpen: false, x: 0, y: 0, clipboard:null });
const [contextMenuIsOpen, setContextMenuIsOpen] = useState(false);
const openChild = ()=>{
contextMenuState.isOpen = true;
setContextMenuState(contextMenuState);
}
return <div><h1>Hello</h1>
<button onClick={openChild}>Open Child</button>
{contextMenuState.isOpen &&
<h1>hidden</h1> }
</div>
}
export default Parent;
React checks objects for equality by checking their reference.
Simply, look at the below example.
const x = { a : 1, b : 2};
x.a = 3;
console.log(x===x);
So when you do the below,
const openChild = ()=>{
contextMenuState.isOpen = true;
setContextMenuState(contextMenuState);
}
You are not changing the reference of contextMenuState. Hence, there is no real change of state and setContextMenuState does not lead to any rerender.
Solution:
Create a new reference.
One example, is using spread operator:
const openChild = ()=>{
setContextMenuState({ ...contextMenuState , isOpen : true });
}
The problem with your second approach is that React will not identify that the value has changed.
const openChild = () => {
contextMenuState.isOpen = true;
setContextMenuState(contextMenuState);
}
In this code, you refer to the object's field, but the object reference itself does not change. React is only detecting that the contextMenuState refers to the same address as before and from its point of view nothing has changed, so there is no need to rerender anything.
If you change your code like this, a new object will be created and old contextMenuState is not equal with the new contextMenuState as Javascript has created a new object with a new address to the memory (ie. oldContextMenuState !== newContextMenuState).:
const openChild = () => {
setContextMenuState({
...contextMenuState,
isOpen: true
});
}
This way React will identify the state change and will rerender.
State is immutable in react.
you have to use setContextMenuState() to update the state value.
Because you want to update state according to the previous state, it's better to pass in an arrow function in setContextMenuState where prev is the previous state.
const openChild = () =>{
setContextMenuState((prev) => ({...prev, isOpen: true }))
}
Try change
contextMenuState.isOpen = true;
to:
setContextMenuState((i) => ({...i, isOpen: true}) )
never change state like this 'contextMenuState.isOpen = true;'
Is there way to change the index dynamically? or rebuild this object to where the 1 will be the Id of what ever object get passed into the function? Hope this makes sense.
export const createTree = (parentObj) => {
//keep in memory reference for later
const flatlist = { 1: parentObj }; <---- change the 1 based on parent.Id
...
}
My attempt thinking it would be easy as:
const flatlist = { parentObj.Id: parentObj };
Use computed property names to create a key from an expression:
const createTree = (parentObj) => {
const flatlist = { [parentObj.id]: parentObj };
return flatlist;
}
console.log(createTree({ id: 1 }));
Is there a way where I can use for loops and if statements without breaking the hook rule? To elaborate, I am currently trying to compare two lists (allData and currentSelection) and if there are similarities, I will add them to another list (favData). However, I am constantly either having visibility issues or errors. If I can get some help, I would much appreciate it!
const [favData, setFavData] = useState([]);
useEffect(() => {
getFilterFavMeal();
}, []);
function getFilterFavMeal() {
allData.forEach((mealList) => {
currentSelection.forEach((mealList2) => {
if (mealList["menu_item"]["menu_item_id"] === mealList2.value) {
// with push, I have visibility issues
// favData.push(mealList);
setFavData(mealList);
}
});
});
setFavData(favData);
}
The set function that useState returns updates the state and schedules a re-render of the component so that the UI can update. It doesn't make sense to call the set function many times in one render.
You also don't want to mutate React state by using functions like push.
Since it looks like favData is deterministic, you can simply remove it from the component state and calculate it in the render loop.
const favData = allData.filter(a => currentSelection.some(c => c.value === a.menu_item.menu_item_id));
Answering your original question, of course you can use loops. As long as you don't mutate the existing state object. And don't set the state more than once per render.
const FF = () => {
const [list, setList] = useState([]);
const addStuffToList = () => {
const tail = Array.from(new Array(3)).map((_e, i) => i);
// Build a new array object and use that when setting state
setList([...list, ...tail]);
}
const forLoop = () => {
const tail = [];
for (let i = 0; i < 4; i++) {
tail.push(i);
}
// Same thing
setList([...list, ...tail]);
}
return ...
};
I'm pretty new in React Development. I want to pass several temperature values that come from props as initialState (via openWeather api), and then use the setTemperature to change with an onClick the values to their farenheit equivalent.
My code looks like this, and while the initial receives the values, the hook doesn't:
const initialState = [tempCelsius, minTemp, maxTemp];
const [temperature, setTemperature] = useState(initialState)
const celToFar = () => {
setTemperature((parseInt(initialState) * 9) / 5 + 32);
};
I tried several other ideas, including this, which returns object object:
const initialState = [tempCelsius, minTemp, maxTemp];
const [temperature, setTemperature] = useState(prevState => {
return {...prevState, initialState}
})
const celToFar = () => {
setTemperature((parseInt(initialState) * 9) / 5 + 32);
};
What am I doing wrong? I'm pretty certain I don't need useEffect and should be able to do it if I pass the array correctly to the hook, since in console.log the function works when applied to individual props.
Welcome to SO! :)
parseInt(initialState) will turn you array [1,2,3] to 1...
Change your function to this:
const celToFar = () => {
let farengeits = initialState.map(v => v * 9 / 5 + 32);
setTemperature(farengeits);
};
To render values you can use:
return (
<div>{temperature.map((f, idx) => <div key={idx}>{f} F</div>}</div>
)
I need to make this function reusable but I don't understand how setState will be passed to be available in it
function getRandomEmployee(updateStateFn){
const filteredEmployee = employeeList.filter(image => image.hasImage === true)
const random = filteredEmployee[Math.floor(Math.random() * filteredEmployee.length)]
const randomOpt1 = filteredEmployee[Math.floor(Math.random() * filteredEmployee.length)]
const randomOpt2 = filteredEmployee[Math.floor(Math.random() * filteredEmployee.length)]
const randomOpt3 = filteredEmployee[Math.floor(Math.random() * filteredEmployee.length)]
const randomOptions = [random.fullName, randomOpt1.fullName, randomOpt2.fullName, randomOpt3.fullName]
randomOptions.sort(() => { return 0.5 - Math.random() })
setState(state => {
const newState = updateStateFn && updateStateFn(state)
return {...newState, randomEmployee: random, randomOptions: randomOptions, playerIsWin:'', disableFieldset: false}
})
}
I expect the function to output random 4 names and return new states on setState
I would make this function pure and use it when you need to generate these random names, e.g.
class SomeComponent extends Component {
handleClick = () => {
const {random, randomOptions} = getRandomEmployee()
this.setState({
randomOptions,
random,
})
}
}
I've noticed a few things here:
1) You have some repetitive code -> filteredEmployee[Math.floor(Math.random() * filteredEmployee.length)] It would be a good idea to abstract it out. You could do this like:
function getRandomIndex(dataArray) => dataArray[Math.floor(Math.random() * dataArray.length)]
Then you could just call the function like: const randomOption = this.getRandomIndex(filteredEmployee)
2) For your setState to work, it depends on where this method of yours is located. Is it inside of the component that is handling the state? If it's not, one option is to have your getRandomEmployee simply return the object you need and have the component invoking it setState instead.