For loops and if statements in Hooks? - javascript

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

Related

filtering an array issue

I'm importing an array into a module, and adding and removing items from that array. when I give a push, it adds the item to the array globally, so much so that if I use that same array in another module, it will include this item that I pushed. but when I try to filter, with that same array getting itself with the filter, it only removes in that specific module. How can I make it modify globally?
let { ignore } = require('../utils/handleIgnore');
const questions = require('./quesiton');
const AgendarCollector = async (client, message) => {
ignore.push(message.from);
let counter = 0;
const filter = (m) => m.from === message.from;
const collector = client.createMessageCollector(message.from, filter, {
max: 4,
time: 1000 * 60,
});
await client.sendText(message.from, questions[counter++]);
collector.on('start', () => {});
await collector.on('collect', async (m) => {
if (m) {
if (counter < questions.length) {
await client.sendText(message.from, questions[counter++]);
}
}
});
await collector.on('end', async (all) => {
ignore = ignore.filter((ignored) => ignored !== message.from);
console.log(ignore);
const finished = [];
if (all.size < questions) {
console.log('não terminado');
}
await all.forEach((one) => finished.push(` ${one.content}`));
await client.sendText(message.from, `${finished}.\nConfirma?`);
});
};
module.exports = AgendarCollector;
see, in this code, import the ignore array and i push an item to then when the code starts and remove when its end.
but the item continues when I check that same array in another module.
I tried to change this array ignore by using functions inside this module but still not working
let ignore = [];
const addIgnore = (message) => {
ignore.push(message.from);
};
const removeIgnore = (message) => {
ignore = ignore.filter((ignored) => ignored !== message.from);
console.log(ignore);
};
console.log(ignore);
module.exports = { ignore, addIgnore, removeIgnore };
You are using the variables for import and export and hence cought up with issues.
Instead, make use of getters.
Write a function which will return the array of ignore. something like this:
const getIgnoredList = () => {
return ignore;
};
and in your first code, import getIgnoredList and replace ignore with getIgnoredList()
Explanation :
Whenever we import the variables only the value at that particular time will be imported and there will not be any data binding. Hence there won't be any change in the data even though you think you are updating the actual data.
When you use require(...) statement it's executed only once. Hence when you try to access the property it gives the same value everytime.
Instead you should use getters
let data = {
ignore : [],
get getIgnore() {
return this.ignore
}
}
module.export = {
getIgnore: data.getIgnore,
}
Then wherever you want to access ignore do
var {getIgnore}= require('FILE_NAME')
Now: console.log(getIgnore) will invoke the getter and give you current value of ignore
Using getters will allow you to access particular variables from other modules but if you want to make changes in value of those variables from other module you have to use setter.
More about getters here
More about setters here

How to replicate useState with vanilla JS

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());

Removing items from state by timer

There is a local state (hook), it has an array of four elements. There is a button on the screen to add a new element to this array. When a component is loaded, in useEffect called method that removes the first element from the state every 5 seconds. If you do not touch the button that adds a new element to the state, then everything works great. But if you start adding elements, the deletion works according to the previous state, only then the state with the new element is displayed. Tell me how to fix it so that everything works stably. I understand what needs to be sought in the direction of the life cycle, a conflict of states occurs, but I can not find a solution.
const Component = () => {
const [arr, setArr] = useState(['one', 'two', 'three', 'four']);
React.useEffect(() => {
console.log("render");
setTimeout(deleteElementFromArr, 5000)
});
const addNewElementToArr = () => {
let temp = arr.slice();
temp.push('newElement');
setArr(temp);
};
const deleteElementFromArr = () => {
if (arr.length > 0) {
console.log(arr);
let temp = arr.slice();
temp.splice(0, 1);
setArr(temp)
}
};
return (<div>
<div>
<Button onClick={addNewElementToArr}>add</Button>
</div>
<div style={{margiTop: '10px'}}>
{arr.map(a => `${a} `)}
</div>
</div>)
};
https://codepen.io/slava4ka/pen/WNNvrPV
In your useEffect hook, when the effect is finished, clear the timeout. When the state is changed, it will trigger again with the new value of the state.
React.useEffect(() => {
console.log("render");
const timer = setTimeout(deleteElementFromArr, 5000);
return () => clearTimeout(timer);
});

Updating State React

The following image represents an object with two ui controls that are stored as this.state.controls
Initially the statesValue values are set via data that is received prior to componentDidMount and all is good. However updates to the each of the controls statesValues are sent via an event , which is handled with the following function
const handleValueStateChange = event => {
let controls = Object.entries(this.state.controls);
for (let cont of controls) {
let states = cont[1].states;
if (states) {
let state = Object.entries(states);
for (let [stateId, contUuid] of state) {
if (contUuid === event.uuid) {
cont[1].statesValue[stateId] = event.value;
}
}
}
}
};
which updates the values happily, however bearing in mind the updated values that change are a subset of this.state.controls, I have no idea how to use this.setState to update that that has been changed.
thanks in advance for any pointers
Instead of using Object.entries try destructuring to keep the reference to the objects.
And have a look at lodash. There are some nice helper functions to iterate over objects like mapValues and mapKeys. So you can keep the object structure and just replace the certain part. Afterwards update the whole state-object with the new one.
const handleValueStateChange = event => {
let {controls} = this.state;
controls = _.mapValues(controls, (cont) => {
const states = cont[1].states;
if (states) {
_.mapValues(states, (contUuid,stateId) => {
if (contUuid === event.uuid) {
cont[1].statesValue[stateId] = event.value;
}
});
}
return cont;
});
this.setState({controls});
};
Code is not tested but it should work like this.
Problem is you're updating an object which you've changed from it's original structure (by using Object.entries). You can still iterate in the same way however you'll need to update an object that maintains the original structure. Try this:
Make a copy of the controls object. Update that object. Replace it in state.
const handleValueStateChange = event => {
// copy controls object
const { controls } = this.state;
let _controls = Object.entries(controls);
for (let cont of _controls) {
let states = cont[1].states;
if (states) {
let state = Object.entries(states);
for (let [stateId, contUuid] of state) {
if (contUuid === event.uuid) {
// update controls object
controls[cont[0]].statesValue[stateId] = event.value;
}
}
}
}
}
// replace object in state
this.setState({controls});
};

Refactoring to make a function reusable with setState

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.

Categories