Refactoring to make a function reusable with setState - javascript

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.

Related

why does the property of the external function inherit to the private variable of the javascript function

Try to use functional programming to create an object with external functions to reduce memory usage.
The function is
//increment no of test cases
function incrNoOfTestCases(inputObj){
let hits = inputObj.hits;
console.log(`function says: ${hits}`)
return {...inputObj, hits: (hits || 0) + 1};
}
The creator function is
const test = function(testDecription){
let state = {hits:0};
state.getState = ()=> testDecription;
state.incHits = () => state = incrNoOfTestCases(state);
state.getHits = () => state.hits || 0;
return state;
}
When I do the following test, I can change the hits by assigning a property with to the function.
test1.incHits().hits=10; //mutable!!
console.log(test1.getHits()); //gives 10
console.log(test1.incHits().hits); //gives function says: 10 and then 11
test1.hits=20; //immutable
console.log(test1.getHits()); //gives 10
I tried various alternatives, finally came up with declaring the function to increment the testcases in the creator function. I am looking for an explanation why the property is mutable not for a working case.
In the first version the function was
function incrNoOfTestCases(inputObj){
return {...inputObj, hits: (inputObj.hits || 0) + 1};
}
In this case I also expected the inputObj.hits not to be mutable by incrNoOfTestCases.hits, but wasn't either.
It seems JavaScript firstly assigns incrNoOfTestCases.hits to state before executing the function. Is this correct? Can you explain why?
There is nothing functional about this code. In functional programming you don't want small logical units to handle their state independently. That's OOP. Using a closure is just the same as using a class if you mutate the value.
This is more functional although it probably doesn't work the way you would like.
const Test = (description, hits = 0) => ({
getState: () => description,
incHits: () => Test(description, hits + 1),
getHits: () => hits
})
const test1 = Test('description')
const test2 = test1.incHits(); // incHits returns a new instance of Test
console.log(test2.getHits())
And this would have done the same thing
class Test {
constructor(description, hits = 0) {
this.description = description;
this.hits = hits;
}
static of (description) { return new Test(description) }
getState () { return this.description}
incHits () { return new Test(this.description, this.hits + 1); }
getHits () { return this.hits }
}
const test1 = Test.of('description');
const test2 = test1.incHits();
Yet another way to do it
const Test = (description, hits = 0) => ({ description, hits, type: 'Test' });
export const getState = ({ description }) => description;
export const incHits = ({ description, hits }) => Test(description, hits + 1);
export const getHits = ({ hits }) => hits;
export const of = (description) => Test(description);
import * from './Test'
const test1 = Test.of('description');
const test2 = Test.incHits(test1);

Function doesn't return correct state

I am trying to write a function to get a random color from an array COLORS. But, the problem is instead of returning only one color, it is returning too many colors, resulting in an infinite error. Resulting in which react throws Too many re-renders error. What's wrong with my code?
const COLORS = [
"#FFAEBC",
"#1ec891",
"#ff725e",
"#ffd05b",
"#A0E7E5",
"#5885AF",
];
const DisplaySubject = (props) => {
const [randomColor, setRandomColor] = useState("");
const generateRandomColor = (colors) => {
let randColorIndex, randColor;
for (let i = 0; i < colors.length; i++) {
randColorIndex = Math.floor(Math.random() * colors.length);
randColor = colors[randColorIndex];
return randColor;
}
console.log(randColor);
setRandomColor(randColor);
};
generateRandomColor(COLORS);
console.log("random color state :", randomColor);
return (
<div>
Let's get started
</div>
);
};
export default DisplaySubject;
First, fix your color generator function:
const generateRandomColor = (colors) => {
const randColorIndex = Math.floor(Math.random() * colors.length);
return colors[randColorIndex];
};
Then you can use it as the initial value for useState:
const DisplaySubject = (props) => {
const [randomColor, setRandomColor] = useState(generateRandomColor(COLORS));
return (
<div style={{ backgroundColor: randomColor }}>
Let's get started
</div>
);
};
The problem was to call setRandomColor inside the getRandomColor function, that caused an infinite render cycle.
You don't need to iterate through the array to get a random element. Change your function like this:
const generateRandomColor = (colors) => {
let randColorIndex, randColor;
randColorIndex = Math.floor(Math.random() * colors.length);
randColor = colors[randColorIndex];
console.log(randColor);
setRandomColor(randColor);
return randColor;
};
And the function gets called every time the component rerenders. So call the function on an event like onClick or if you want to run the function only once when the component renders for the first time, use useEffect:
useEffect(() => generateRandomColor(COLORS), [])
Every time your component mounts, the function generateRandomColor() is called and then it updates the state which causes a new render.
As an option, you can create a button and call this function with onClick event:
<button onClick={generateRandomColor(COLORS)}>generate a color</button>

For loops and if statements in Hooks?

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

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

Can't pass an initial array to a useState to modify it later

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

Categories