I'm creating a DoS-like typing effect dialogue for my website. I've got the effect running but it seems to skip the second render of the loop.
I've got an effect hook that adds each letter from a string to a new state via an index ref until the index ref equals the length of the string. Here's that (and a stackblitz of it);
const startingLine = `Lorem ipsum.`;
const [screenResponse, setScreenResponse] = useState('');
const [skipDialogue, setSkipDialogue] = useState(false);
let index = useRef(0);
useEffect(() => {
let addChar;
function tick() {
setScreenResponse((prev) => prev + startingLine[index.current]);
index.current++;
console.log(screenResponse);
}
if (index.current < startingLine.length - 1 && !skipDialogue) {
addChar = setInterval(tick, 500);
return () => clearInterval(addChar);
} else {
setScreenResponse(startingLine);
}
}, [screenResponse, skipDialogue, startingLine]);
The screenResponse is what shows on screen, except the 'o' from 'Lorem' would be missing until the effect is finished (and is still missing without the else line).
I've taken it out of React.StrictMode, and it happens in all string lengths except one, and happens no matter how long the interval takes.
I need the startingLine.length - 1 so that it doesn't drop an undefined at the end, but removing that doesn't change the missing second render.
With the console.log in the tick interval, it doesn't print 'Lo' just straight to 'Lr'. So, why is the second render being skipped and how can I fix it?
working code : https://stackblitz.com/edit/react-jed2pz?file=src/App.js
try using useState instead of ref
this is from react docs
more info about useRef :https://reactjs.org/docs/hooks-reference.html#useref
Change this lines in tick():
const char = startingLine.charAt(index.current);
setScreenResponse(prev => `${prev}${char}`);
Related
Edit Note:Link with working version at bottom of post
I have run into some really weird behavior with my project
I have the parent which gets some data from a database and passes it to the child (which is a popup). The child then uses useEffect to detect when the data is ready (black part) and sets the value in the child (blue part).
useEffect(() => {
console.log(datachannels.length)
if (datachannels.length > 1) {
let newValueState = []
let c = 0
datachannels.forEach(element => {
newValueState.push({ id: c, dc: element.dataChannel, axis: "y" })
c += 1
});
setValueState(newValueState)
console.log(newValueState)
console.log("trigger")
}
}, [datachannels])
Then when a user changes a option this code runs to update it(Green part)
const handler = (event, id) => {
console.log(valueState)
const value = event.value
console.log(valueState.length)
if (valueState.length > 0) {
let newValueState = valueState
newValueState.forEach(key => {
if (key.id == id) {
console.log(key)
key.axis = value
}
});
setValueState(newValueState)
}
}
With inspect i can see that the value is not empty but the handler function trying to use it is telling me its empty. When giving valueState a default value and after useEffect code runs, I try and access it but it just returns the default value. I have no idea what is causeing this or what to google so if anyone can shead some light on this, I would be very happy
Update 1: Codesandebox (new link below) I have not been able to get the bug to show yet
Update 2:Code in sandbox now has error. As you can see when useEffect runs the object has 3 values but when the user changes the dropdown it only has one value
Update 3: To see the bug in the sandbox change one of the axis drop downs
Update 4: Porok12 answer works in the sand box but is not working in my actully project and i have no idea what is going on
Update 5: So when using Porok12 code and feeding in datachannels the same way as the sandbox it does work but with some other weird behavior that im hopping can point to the problem. When changing a dropdown this is outputted to the console. From input was changed to (3) [{},{},{}] is the correct behavior but then everything rerenders as seen from the other messages. I have no idea why it is behaving differently from porok12 code as it is the same. But this does point out a problem with how datachannels is being passed in? In the parent object I am storing the channels like this
if (data !== undefined) {
let parseData = JSON.parse(data);
let toPush = []
Object.keys(parseData).forEach(key => {
toPush.push({ "dataChannel": key })
});
setDataChannels(toPush);
And feeding it to the child like this <BuilderTable handleupdate={handleUpdate} datachannels={dataChannels} show={modalShow} onHide={() => setModalShow(false)} />
Update 6: So its now working with Porok12 code after i changed the useMemo [] to use valueState instead of datachannel. The only problem now is that the dropdown reset to the starting value every time you change it
Update 7: With the change from update 6 and feeding valueStatus into the dropdowns the problem is not fixed.
Update 8: https://codesandbox.io/s/spring-water-2ucq8u?file=/src/GraphBuilderFull.js Here is the sandbox link with a working version
Side note: you can change
let c = 0
datachannels.forEach(element => {
newValueState.push({ id: c, dc: element.dataChannel, axis: "y" })
c += 1
});
to
const newValueState = datachannels.map((element, index) => ({id: index, dc: element.dataChannel, axis: "y"}))
The problem is that you use useMemo that way:
const selectChannelTableColumn = React.useMemo(
() => [
{
Header: "Axis Selection",
Cell: ({ row }) => (
<div>
<Dropdown
id={row.id}
options={axis}
onChange={(e) => handler(e, row.id)}
placeholder="y"
/>
</div>
)
}
],
[] // missing `datachannels` xor `valueState` dependency
);
If you will add datachannels to dependencies it will work. You should also move handler definition to this useMemo code.
Here is fixed sandbox example
The changes I made:
Move handler definition to selectChannelTableColumn code
Fix handler logic
Move selectChannelTableColumn under (meaning below not inside) your useEffect
Add datachannels to useMemo hook dependencies
I have two objects that I'm trying to loop through using an increment and decrement function. I have the variables being stored in localStorage and the expectation is that no matter which value has been selected last, upon refresh or reload, the counter should still work. However, I've experienced some unexpected occurrences upon using the increment and decrement functions where the variable does not line with the index of the object as shown by the reducer state.
const increment = (props) => {
return (props.state + 1) % props.length;
};
const decrement = (props) => {
return (props.state - 1 + props.length) % props.length;
};
const colors = [
{ id: 0, type: "Red" },
{ id: 1, type: "Blue" },
{ id: 2, type: "Green" },
{ id: 3, type: "Yellow" }
];
For example, at times when I call the state, it will tell me that the color is Yellow and the index is 2, and is generally inconsistent. I've tried storing the counter variable within localStorage and calling from that in hopes that it will synchronize the counter with the intended variable, however, this has not worked.
Here is a demo with CodeSandbox. I'm still relatively new to React and I'm not sure if using a counter is the best method for this problem.
Here is a reproducible example of the output I've received (upon clearing localStorage and refreshing the app) and the expected output for the shapes.
Output:
Square (next) Square (previous) Circle (previous) Square (previous) Octagon (next) Triangle
Expected output:
Square (next) Circle (previous) Square (previous) Octagon (previous) Triangle (next) Octagon
Having forked and refactored your original sandbox here there were a few challenges.
Firstly, the reducer actions were expanded to include increment and decrement. The example provided in React Hooks Reference has an example of incrementing and decrementing, though reducers are not just applicable for counting. The dispatch of your reducer will set the state, so having Action.Set is redundant.
Let's take a look at one of the original buttons' onClick methods. Originally, colorCount was being decremented and then an update to color occurs based on the state at the time of click, not when the state is updated. To visualize this on the original demo, log the state before and after setData.
onClick={() => {
setData({
payload: decrement({
state: state.colorCount,
length: colors.length
}),
name: "colorCount"
});
setData({
payload: colors[state.colorCount].type,
name: "color"
});
}}
Now, the same onClick calls the decrement method.
onClick={() => {
decrement({
name: "colorCount"
});
}}
The decrement method, moved to the context, just calls the dispatch with proper type and payload containing the name of the value to update.
const decrement = (payload) => {
dispatch({
type: ACTIONS.DECREMENT,
payload
});
};
Lastly, the reducer updates the states colorCount paired with its prefix color and shapeCount paired with its prefix shape
const reducer = (state, action) => {
// Verify which value we need to update along with its count
const isColor = action.payload.name.includes("color");
// Select the proper option in context
const options = isColor ? colors : shapes;
switch (action.type) {
case ACTIONS.INCREMENT:
// Increment the count for use in the value and type setting
const incrementedCount =
(state[action.payload.name] + 1) % options.length;
return {
...state,
// Set the new count
[action.payload.name]: incrementedCount,
// Set the new color or shape type
[isColor ? "color" : "shape"]: options[incrementedCount].type
};
case ACTIONS.DECREMENT:
// Decrement the count for use in the value and type setting
const decrementedCount =
(state[action.payload.name] - 1 + options.length) % options.length;
return {
...state,
// Set the new count
[action.payload.name]: decrementedCount,
// Set the new color or shape type
[isColor ? "color" : "shape"]: options[decrementedCount].type
};
default:
return state;
}
};
As far as updating the localStorage on update of a value, the easiest way is another useEffect dependent on the state values. Feel free to update the localStorage how and when you want, but for the purposes of keeping the state on reload the simplest approach was kept.
useEffect(() => {
localStorage.setItem("colorCount", JSON.stringify(state.colorCount));
localStorage.setItem("color", JSON.stringify(state.color));
localStorage.setItem("shapeCount", JSON.stringify(state.shapeCount));
localStorage.setItem("shape", JSON.stringify(state.shape));
}, [state.colorCount, state.color, state.shape, state.shapeCount]);
To the point made about contexts, this counter example does benefit from simplicity. The reducer can be used all within the App. Contexts are best used when passing down props to children becomes cumbersome.
What you've provided here is not sufficient for a minimum reproducible example. We can't offer much help if your problem is only happening "at times" -- please provide specific cases of what steps you take to obtain a specific problem.
Generally speaking, I think it might simplify your code to use two separate state variables. Context seems like overkill for this use case.
const [colorIdx, setColorIdx] = useState(0);
const [shapeIdx, setShapeIdx] = useState(0);
And, as a style note, it is usually a good idea to avoid inline function definitions. The following is much more readable, for example:
const incrementColorIdx = () => {
setColorIdx((colorIdx + 1) % colors.length);
}
...
<button onClick={incrementColorIdx}>Next</button>
When you push an array, data is pushed. However if you check in console.log, data is not imported. It seems to be a delay. Can you tell me why is this happening?
is there solution for this?
Expected console.log result showing input, however empty array shows and if you click checkbox again then input appears.
const [checked, setChecked] = useState<number[]>([])
const handleAddListToArray = (id: number) => {
console.log(checked)
if (setChecked.includes(id)) {
setChecked(checked.filter((item) => item !== id))
} else {
setChecked([...checked, id])
}
}
--- checkbox compornent ---
const [isChecked, setIsChecked] = useState(false)
const handleChange = () => {
setIsChecked(!isChecked)
handleAddListToArray(id)
}
<Checkbox checked={isChecked} onClick={() => handleChange()} />
when you push an array, data is pushed however if you check in
console.log data is not inported. it seems to be a delay can you tell
me why is this happening?
The state-setting functions are asynchronous. In other words, if you wrote:
const [foo, setFoo] = useState(0);
setFoo(1);
console.log(foo); // logs 0, NOT 1
you would see 0 logged, not 1 ... at least initially. However, there'd be a log entry below that would show 1.
This is because set* function don't change the value in the function, but they do cause the component to be re-rendered after, which means the function is run again, and now uses the correct value..
however empty array shows and if you click checkbox again then input
appears.
It's because of this code:
setIsChecked(!isChecked)
Initially you set isChecked to an array:
setChecked(checked.filter((item) => item !== id))
But then you changed it to !isChecked ... a boolean. Once you change it to a boolean, you can't change it back to an array.
You check the setState-function if it includes the input, on your fourth row of code:
if (setChecked.includes(id))
I believe you want to chech the checked-state instead, like so:
if (checked.includes(id))
Also, consider using the useState-callback when you mutate your state based on the previous one. So instead of:
setChecked(checked.filter((item) => item !== id))
try:
setChecked((prevState) => prevState.filter((item) => item !== id))
You can also use this when you setting your isChecked state. This ensure that you get your latest known state and avoids getting in some wierd states because React updates the state asynchronous.
Some suggestions
if (setChecked.includes(id)) {
should be (setChecked is a function)
if (checked.includes(id)) {
For setChecked(checked.filter((item) => item !== id))
better usage will be
setChecked(prevCheckedValues => prevCheckedValues.filter((item) => item !== id));
This way you will always get the current checked values when you do a setChecked.
Another way to use this is via a useCallback and pass the dependency array as [checked]
If you want to print checked values each time its changed you can use a useEffect (docs) with correct dependency array.
Usage example
useEffect(()=>{
console.log("Checked", checked);
}, [checked])
So I'm trying to make a screen where data from user's localstorage is used (Lets call it var1) but I'm getting Error: Too many re-renders. React limits the number of renders to prevent an infinite loop. error. What I'm trying to do is check if the data from user's localstorage exists and if it does then it will put that data into a state but first it will grab another variable from localstorage (User auth token, lets call it var2) and put it into every object in var1 (var1 is a list which contains objects) and this is done using map then the state is set to the changed var1 with the auth token(or var2), then it returns some HTML and some logic is used in HTML, For every object in var1 it will create a new select tag with numbers ranging from 1 to 20 and this is done using mapping an array with 20 numbers (I'm doing this because I could not get for loop to work properly) and if the current number of option in select tag matches a key value pair in one of var1's object then it will
select the option tag or put selected attribute on option tag and if you change the value of select tag then it will trigger a function which will map through var1 and select the object which user requested and change the value of quantity to whatever the user selected on select tag. I tried to cut down and simplify my code as much as I could. My code is like this:
function RandomScreen() {
const [var1, setvar1] = useState([])
let localstoragevar = JSON.parse(localStorage.getItem('var'))
let newCart = []
if (localstoragevar) {
localstoragevar.map(item => {
item.authtoken = localStorage.getItem('AuthToken')
newCart.push(item)
})
}
setvar1(newCart)
let twenty = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
return (
{var1.map(newItem => {
{/* HTML code goes here */}
{twenty.map(number => {
if (number == item.quantity) {
return (
<option onChange={handleClick} selected name={newItem.id} value={newItem.quantity}>{newItem.quantity}</option>
)
} else {
return (
<option onChange={handleClick} name={newItem.id} value={number}>{number}</option>
)
}
})}
})}
)
}
Your render calls setvar1 which in turn trigger's a re-render.
You should put this whole logic inside a useEffect hook:
useEffect(() => {
let localstoragevar = JSON.parse(localStorage.getItem('var'))
let newCart = []
if (localstoragevar) {
localstoragevar.map(item => {
item.authtoken = localStorage.getItem('AuthToken')
newCart.push(item)
})
}
setvar1(newCart)
}, []);
This is what you have to do is to avoid logic in your render function. For that case, we do have useEffect function plus on top of that you may add useMemo;
I am learning react through the university of helsinki's full stack open course. The point of the code is there are two buttons and a quote shown on page. One button goes to a random quote and the other lets you put in a vote and shows how many votes that quote has total. The problem is that when I click vote, it adds the vote in the background but doesn't re-render the total amount of votes unless I change the quote.
I've tried different ways to go about conducting state change such as creating a function specifically for the setVote but I can't get it to work.
const App = () => {
const [selected, setSelected] = useState(0)
let [vote, setVote] = useState([...copyVote])
const changeAnecdote = () => {
setSelected(Math.floor(Math.random() * 6))
}
const addVote = () => {
copyVote[selected] = copyVote[selected] + 1
setVote(vote = copyVote)
}
return (
<div>
<Button onClick={changeAnecdote} text='Next Anecdote'/>
<Button onClick={addVote} text='Vote'/>
<div>
{anecdotes[selected]}
<DisplayVotes vote={vote} selected={selected}/>
</div>
</div>
)
}
copyVote is a copy of a zero-filled array and DisplayVotes simply shows how many votes total for that quote on screen.
When I check for changes in the array of votes after hitting vote through developer tools, the array doesn't change until I go to another quote.
Anyone have any ideas as to what I'm doing wrong?
There is a few things that should be updated here.
Firstly, copyVote is an array containing the number of votes for each quote. So ...copyVote will give you each item in that array. You only want the vote for the current quote. Your initial vote state should be
const [vote, setVote] = useState(copyVote[selected])
You also want to update the addVote function the way DSCH mentioned.
const addVote = () => {
// copyVote[selected]++ is the same as copyVote[selected] += 1 and copyVote[selected] = copyVote[selected] + 1
setVote(copyVote[selected]++)
}
Finally, you want to add a way to update the vote each time the anecdote is changed. You could do this in the changeAnecdote function, but a better approach would be to use an effect hook that is dependent on the selected state.
useEffect(() => {
// Set the value of vote to match the newly selected quote
setVote(copyVote[selected])
}, [selected])
Using this, the DisplayVotes vote prop is only going to display the vote for the currently selected quote. You may need to updated that component to handle this.
In setVote(vote = copyVote) the expression within the parentheses is an expression to set the vote variable with the value in copyVote. I assume that's why you use let in let [vote, setVote] = useState([...copyVote]), since probably you got an error setting a value to a const.
As setVote is what return from the useState what you probably want to do is:
const addVote = () => {
copyVote[selected] = copyVote[selected] + 1
setVote(copyVote[selected])
}