I am converting a code from class to functional component. I am unsure as to how I would convert the code below into a hooks function.
applyFilter = () => {
const { filterLimit: value, lessThanOrGreaterThan } = this.state;
const isLessThan = lessThanOrGreaterThan === "lessThan";
// update instance variable
this.state.datas = this.state.datas.map(v => {
if (isLessThan ? v <= value : v >= value) return v;
return 0;
});
this.setState(prevState => ({
datas: this.state.datas,
}));
}
I have declared the used variables as hooks as follows-
const [filterLimit, setfilterLimit] = useState(100);
const [lessThanOrGreaterThan, setlessThanOrGreaterThan] = useState('lessThan');
const [datas, setDatas] = useState([12, 19, 3, 5, 2, 10])
Looks like you've got most of is. Looks like you just need to swap out the references to state with the props you've already defined.
const [filterLimit, setfilterLimit] = useState(100);
const [lessThanOrGreaterThan, setlessThanOrGreaterThan] = useState('lessThan');
const [datas, setDatas] = useState([12, 19, 3, 5, 2, 10])
const applyFilter = () => {
const isLessThan = lessThanOrGreaterThan === "lessThan";
// update instance variable
const newDatas = datas.map(v => {
if (isLessThan ? v <= value : v >= value) return v;
return 0;
});
setDatas(newDatas);
}
Related
I'm trying to get back the state value from this useState Vanilla JS replica:
let callIndex = -1
const stateValues = []
const useState = (initialValue) => {
callIndex++ // 0
const currentCallIndex = Number(callIndex) // 0
if (stateValues[currentCallIndex] === undefined){
stateValues[currentCallIndex] = initialValue
}
const setValue = (newValue) => {
stateValues[currentCallIndex] = newValue
}
return [stateValues[currentCallIndex], setValue]
}
const [countA, setCountA] = useState(1)
const [countB, setCountB] = useState(-1)
This useState() function returns an initializing value and storing it in the stateValues array. But it doesn't actually refer to the array position.
setCountA(3)
// LOGs-> stateValues[0]: 3, stateValues[1]: -1, countA: 1, countB: -1
setCountA(5)
setCountB(9)
// LOGs-> stateValues[0]: 5, stateValues[1]: 9, countA: 1, countB: -1
setCountA(2)
setCountB(5)
// LOGs-> stateValues[0]: 2, stateValues[1]: 5, countA: 1, countB: -1
Now, I can make useState return an arrow function that returns stateValues[currentCallIndex] but then I have to call a function every time I need a state value.
Is there a more Reactsy way of returning the updated value by reference (countA rather than countA() )?
If you insist on doing it the React way without implementing an actual component lifecycle logic, you can do something like this:
let callIndex = -1
const states = []
const useState = (initialValue) => {
const currentCallIndex = callIndex++
states[currentCallIndex] = states[currentCallIndex] ?? {
value: initialValue,
setValue: (newValue) => {
states[currentCallIndex].value = newValue;
},
}
return states[currentCallIndex]
}
let stateA = useState(1)
let stateB = useState(-1)
console.log(stateA.value, stateB.value) // 1, -1
stateA.setValue(2)
stateB.setValue(3)
console.log(stateA.value, stateB.value) // 2, 3
// ======= now let's reset the index =======
callIndex = -1;
// ======= and check that the state is persistent =======
stateA = useState(1)
stateB = useState(-1)
console.log(stateA.value, stateB.value) // 2, 3
I'm making MBTI app with ReactJS
But I have some problem now
When I click button i got some string ex 'E or I'
and then When it finished I got String value ex'EEINNSTTFPPJ'
so I want to change this value to 'ENTP'
How Can I make it ? 1.state
const TOTAL_SLIDES = 12
const [score, setScore] = useState(0)
const [type, setType] = useState([])
const [num, setNum] = useState(0)
const [currentSlide, setCurrentSlide] = useState(1)
const slideRef = createRef(null)
const history = useHistory()
const [mbti, setMbti] = useState('')
2.funtion
const nextSlideFir = () => {
setNum(num + 1)
setType(questions[num].answers[0].type)
setMbti(mbti + type)
setCurrentSlide(currentSlide + 1)
slideRef.current.style.transform += 'translateX(-100vw)'
}
const nextSlideSec = () => {
setNum(num + 1)
setType(questions[num].answers[1].type)
setMbti(mbti + type)
setCurrentSlide(currentSlide + 1)
slideRef.current.style.transform += 'translateX(-100vw)'
}
//I Don't know how to get same duplicate values
const mbitChecker = string => {
const words = [string]
return words.filter((item, index) => words.indexOf(item) !== index)
}
useEffect(() => {
currentSlide > TOTAL_SLIDES &&
mbitChecker(mbti) &&
history.push(`/result/${mbti}`)
})
)
Not sure what you are trying to, but you have better to have useEffect's dependency array
const extractDuplicates = (text) => {
// extractDuplicates('EEINNSTTFPPJ') -> "ENTP"
const ans = []
for (t of text) {
if (text.indexOf(t) !== text.lastIndexOf(t)) {
if (ans.indexOf(t) < 0) {
ans.push(t)
}
}
}
return ans.join('')
}
useEffect(() => {
currentSlide > TOTAL_SLIDES && history.push(`/result/${mbti}`)
console.log(`${mbti}`)
mbitChecker()
}, [mbti])
Consider I'm uploading a bunch of files and I'm trying to keep track of the ones in progress so that whenever an upload is finished, I decrement my state until all are finished and finally I change my uploading state to false:
(Here I made a sleep function to mimic the uploading process).
import React, { useState} from "react";
const sleep = (ms, dev =1 ) => {
const msWithDev = (Math.random() * dev + 1) * ms
return new Promise(resolve => setTimeout(resolve, msWithDev))
}
function Counter() {
const [, setSortEnabled] = useState(true)
const [count, setCount] = useState(0)
const arr = [1, 2, 3, 4, 5]
const upload = async () => {
await sleep(1000)
setCount(c => c - 1)
console.log(`count state: ${count}`)
if (count === 0) {
console.warn('Sort set to enabled again.')
setSortEnabled(true)
}
}
const onClick = () => {
console.warn('Sort set to disabled.')
arr.forEach(item => {
setCount(c => c + 1)
console.error(count)
upload()
})
}
return (
<>
<button onClick={onClick}>Click me!</button>
</>
);
}
export default Counter;
The problem is in upload,
function `console.log(\`count state: ${count}\`)`
always logs 0 which means the state has never been updated.
What am I doing wrong here?
Which means the state has never been updated
It does get updated, you are just logging staled value because of closures.
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function’s scope from an inner function.
To fix it, use setState functional updates:
const onClick = () => {
console.warn("Sort set to disabled.");
arr.forEach((item) => {
setCount((c) => {
console.error(c);
return c + 1;
});
upload();
});
};
Instead of relying on count, you must use Promise.all to wait for all files to be uploaded. In your case count is received from closure so even if the state is updated, the count variable remains unaffected.
You can implement the above logic with Promise.all like
function Counter() {
const [, setSortEnabled] = useState(true)
const arr = [1, 2, 3, 4, 5]
const upload = async () => {
await sleep(1000);
}
const onClick = async () => {
console.warn('Sort set to disabled.')
const promises = arr.map(item => {
console.error(count)
return upload()
});
await Promise.all(promises);
console.warn('Sort set to enabled again.')
setSortEnabled(true)
}
return (
<>
<button onClick={onClick}>Click me!</button>
</>
);
}
export default Counter;
UPDATE:
To get around the closure issue of count state, you can make use of refs and useEffect. However its a workaround and should not be preferred
import React, { useState} from "react";
const sleep = (ms, dev =1 ) => {
const msWithDev = (Math.random() * dev + 1) * ms
return new Promise(resolve => setTimeout(resolve, msWithDev))
}
function Counter() {
const [, setSortEnabled] = useState(true)
const [count, setCount] = useState(0)
const arr = [1, 2, 3, 4, 5]
const countRef = useRef(count);
useEffect(() => {
countRef.current = count;
}, [count ]);
const upload = async () => {
await sleep(1000)
setCount(c => c - 1)
console.log(`count state: ${count}`)
if (countRef.current === 0) {
console.warn('Sort set to enabled again.')
setSortEnabled(true)
}
}
const onClick = () => {
console.warn('Sort set to disabled.')
arr.forEach(item => {
setCount(c => c + 1)
upload();
})
}
return (
<>
<button onClick={onClick}>Click me!</button>
</>
);
}
export default Counter;
I rewrote my class component to a functional component because i had to use react hooks. Its all working fine now except for the screen. Its not updating the text. i've added a ref to the element called screen element and the function setScreenText seems to work. I put some logs and they return what i would expect from them. If I press key 5 console.log(screenElement.textContent) returns 5 and console.log(screenElement) returns {current: div.screen, textContent: "5"} The only problem still is that its not updating the text on the screen, it stays blank.
import React, {useRef, useState} from 'react';
import '../style/App.scss';
import {UPDATE_GAME_STATE} from '../actions';
import { useDispatch } from 'react-redux';
function Keypad() {
const dispatch = useDispatch();
const KEYPAD_STATE_SUCCESS = "keypad_success";
const KEYPAD_STATE_FAILED = "keypad_failed";
const KEYPAD_STATE_INPUT = "keypad_input";
const [state, setState] = useState(KEYPAD_STATE_INPUT);
const tries = [];
const screenElement = useRef(null);
const handleKeyPress = async(value) => {
tries.push(value);
setScreenText(tries.join(''));
if (tries.length >= 4) {
const success = await tryKeyCode(tries.join(''))
const newState = {
setState: success ? KEYPAD_STATE_SUCCESS : KEYPAD_STATE_FAILED
}
const screenText = success ? "SUCCESS" : "FAILED";
const textScreenTime = success ? 3000 : 1000;
const handleGameState = success ? () => onSuccess() : () => onFailed();
setTimeout(() => {
setScreenText(screenText);
setTimeout(handleGameState, textScreenTime);
}, 200);
setState(newState);
}
}
const onSuccess = () => {
dispatch(UPDATE_GAME_STATE('completed'))
}
const onFailed = () => {
console.log("wrong code")
}
/**
* #param {string} text
*/
const setScreenText = (text) => {
screenElement.textContent = text;
console.log(screenElement.textContent); // if key 5 is pressed returns 5
console.log(screenElement); // returns {current: div.screen, textContent: "5"}
}
/**
* #param {string} code
* #returns boolean
*/
const tryKeyCode = async(code) => {
const response = (await fetch("http://localhost:3000/keypad", {
method: "POST",
body: JSON.stringify({
"code": code
}),
headers: {
"Content-Type": "application/json"
}
}));
return response.status === 200;
}
const keys = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const Key = ({number, className = ""}) => <button className={"key " + className} onClick={() => handleKeyPress(number)}>{number}</button>
const keyElements = keys.map(key => (<Key number={key}/>))
return (
<div className="keypad_wrapper">
<div className="screen" ref={screenElement}/>
{keyElements}
<Key className="last" number={0}/>
</div>
);
}
export default Keypad;
As mentioned in the react docs:
useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.
Thus instead of updating the screenElement.textContent you need to update screenElement.current.textContent like:
const setScreenText = (text) => {
if (screenElement.current) {
// Wait until current is available
// `current` points to the mounted element
screenElement.current.textContent = text;
console.log(screenElement.current);
}
}
I'm currently trying to take two objects of objects, where the second one has updated values, and merge the updated values into the first one. I wrote a function to do this but i'm unable to update the values within my AnimatedDataWrapper. However if I run it outside of the AnimatedDataWrapper it works fine..
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import * as d3 from 'd3'
const mapNewStateToOldState = (oldState, newState) => {
Object.keys(oldState).forEach((key) => {
Object.assign(oldState[key], newState[key])
})
return oldState
}
// const mapNewStateToOldState = (oldState, newState) =>
// Object.keys(oldState).map(key => Object.assign(oldState[key], newState[key]))
const obj = { 0: { data: 1 } }
const newObj = { 0: { data: 2 } }
console.log(mapNewStateToOldState(obj, newObj)) // THIS WORKS
console.log(obj) // THIS WORKS
const AnimatedDataWrapper = (dataProp, transitionDuration = 300) => ComposedComponent =>
class extends Component {
constructor(props) {
super(props)
const data = this.props[dataProp]
this.state = Object.keys(data)
.map(label => ({ [label]: data[label] }))
.reduce((prev, curr) => ({ ...prev, ...curr }), {})
}
componentWillReceiveProps(nextProps) {
const data = this.props[dataProp]
console.log(data)
const nextData = nextProps[dataProp]
const dataKeys = this.props.dataKeys
const dataUnchanged = Object.keys(data)
.map(label => data[label] === nextData[label])
.reduce((prev, curr) => prev && curr)
if (dataUnchanged) {
return
}
d3.select(this).transition().tween('attr.scale', null)
d3
.select(this)
.transition()
.duration(transitionDuration)
.ease(d3.easeLinear)
.tween('attr.scale', () => {
const barInterpolators = data.map((...args) => {
const index = args[1]
return dataKeys.map((key) => {
const interpolator = d3.interpolateNumber(
this.state[index][key],
nextData[index][key],
)
return { key, interpolator }
})
})
return (t) => {
const newState = barInterpolators
.map(bar =>
bar
.map(({ key, interpolator }) => ({ [key]: interpolator(t) }))
.reduce((result, currentObject) => {
Object.keys(currentObject).map((key) => {
if (Object.prototype.hasOwnProperty.call(currentObject, key)) {
result[key] = currentObject[key]
}
return null
})
return result
}, {}),
)
.reduce((newObject, value, index) => {
newObject[index] = value
return newObject
}, {})
const oldState = this.state
console.log(`OLD STATE = ${JSON.stringify(oldState)}`)
console.log(`NEW STATE = ${JSON.stringify(newState)}`)
const updatedState = mapNewStateToOldState(oldState, newState) // THIS DOES NOT WORK
console.log(`UPDATED STATE = ${JSON.stringify(updatedState)}`)
this.setState(updatedState)
}
})
}
render() {
const { props, state } = this
const newData = Object.keys(state).map(val => state[val])
const newDataProps = { ...{ data: newData } }
const newProps = { ...props, ...newDataProps }
return <ComposedComponent {...newProps} />
}
}
AnimatedDataWrapper.PropType = {
dataProp: PropTypes.string.isRequired,
transitionDuration: PropTypes.number,
dataKeys: PropTypes.instanceOf(Array).isRequired,
maxSurf: PropTypes.number.isRequired,
}
export default AnimatedDataWrapper
Here is what the objects i'm passing into the function mapNewStateToOldState (oldState, newState) look like. And what the output updatedState looks like.
It seems like maybe it would be a scoping issue? But i can't seem to figure out what is going on. I tried manually merging it with no luck either.
Good ol' Object.assign will do the job you're looking for, where preceding objects will be overwritten by others that follow with the same keys:
var oldState = {a: 1, b: 2}
var newState = {b: 3, c: 4}
Object.assign(oldState, newState) === { a: 1, b: 3, c: 4 }
In stage-3 ecmascript you can use the spread syntax:
var oldState = {a: 1, b: 2}
var newState = {b: 3, c: 4}
{ ...oldState, ...newState } === { a: 1, b: 3, c: 4 }