How do you swap these signals so that b = prev a and a = prev b?
const [a, setA] = createSignal(50)
const [b, setB] = createSignal(100)
function swapSignals() {}
We extract values into local variables before running the state updates otherwise they will end up having the same value, and wrap the update logic into batch to prevent multiple re-renders:
import { batch, createSignal } from 'solid-js';
import { render } from 'solid-js/web';
const App = () => {
const [a, setA] = createSignal(50)
const [b, setB] = createSignal(100)
const swapSignals = () => {
const x = a();
const y = b();
batch(() => {
setA(y);
setB(x);
});
};
return (
<div>
a: {a()} b: {b()}{` `}<button onClick={swapSignals}>Swap</button>
</div>
)
};
render(() => <App />, document.body);
https://playground.solidjs.com/anonymous/f744c2d3-8333-4f63-bf1e-d1e3665f81a2
That was easy than I thought
setA(aPrev => {
const bPrev = b()
setB(aPrev)
return bPrev
})
Related
Goal is to display a real time log that comes from an async function ( this func will return the last log ) and is updated each second. After that i want to accumulate the results on an array and if it get bigger than 5 i want to remove the first element.
Expected: A list of 5 items displayed on screen that is updated each second.
Results: Array is not accumulating above 2 items and the update is random and fast
code ->
const [logs, setLogs] = useState([])
const getLogs = async () => {
const lastLog = await window.getAppLogs()
if (logs.length > 5) {
// here i tried this methods ->
// reduceLogs.shift()
// reduceLogs.splice(0, 1)
const reduceLogs = [...logs, lastLog ]
delete reduceLogs[0]
return setLogs(reduceLogs)
}
const test = [...logs, lastLog] // this line is not accumulating above 2
setLogs(test)
}
useEffect(() => {
setInterval(getLogs, 1000);
}, [])
Updating state in setinterval needs to use the => syntax. So it should be -
setLogs(prevState => [...prevState.slice(-4), lastLog])
Plus you need to clear the interval as well. I've made a demo to display last 5 users when they are updated every second (from api).
export default function App() {
const [users, setUsers] = useState([
"michael",
"jack",
]);
const getUser = async () => {
const response = await fetch("https://randomuser.me/api/?results=1");
const user = await response.json();
return user;
};
const getUsers = async () => {
const user = await getUser();
setUsers(prevState => [...prevState.slice(-4), user.results[0].name.first]);
};
useEffect(() => {
const handle = setInterval(getUsers, 1000);
return () => {
clearInterval(handle);
};
}, []);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
{users.map((log) => (
<div>{log}</div>
))}
</div>
);
}
Update: This should be your getLogs function, no if check, no return etc.
const getLogs = async () => {
const lastLog = await window.getAppLogs()
setLogs((prevState) => [...prevState.slice(-4), lastLog]);
}
hey AG its almost working perfectly, updates are constant now and it is accumulating. But is still not reducing length of the array. Its like u show me:
const [logs, setLogs] = useState([])
const getLogs = async () => {
const lastLog = await window.getAppLogs()
if (logs.length > 4) {
return setLogs((prevState) => [...prevState.slice(-4), lastLog])
}
setLogs((prevState) => [...prevState, lastLog])
}
useEffect(() => {
const handle = setInterval(getLogs, 2500);
return () => {
clearInterval(handle);
};
}, [])
So I was trying to implement a filter that is controlled by a search bar input. So I think part of the problem is that I have this filter hooked on a timer so that while the user is typing into the search bar, it isn't re-running for each letter typed in.
What it is currently doing is that after the item is typed in the search bar, the timer goes off and the filters are working but it doesn't appear that the app is re-rendering with the new filtered variable.
I suspect that it might have something to do with useEffect but I'm having trouble wrapping my head around it and it wasn't working out for whatever I was doing with it.
Here's the code:
const RecipeCards = (props) => {
const inputTypingRef = useRef(null);
let preparingElement = props.localRecipes;
let cardElement;
let elementsSorted;
const ingredientCountSort = (recipes) => {
elementsSorted = ...
}
const elementRender = (element) => {
cardElement = element.map((rec) => (
<RecipeCard
name={rec.name}
key={rec.id}
ingredients={rec.ingredients}
tags={rec.tags}
removeRecipe={() => props.onRemoveIngredients(rec.id)}
checkAvail={props.localIngredients}
/>
));
ingredientCountSort(cardElement);
};
if (inputTypingRef.current !== null) {
clearTimeout(inputTypingRef.current);
}
if (props.searchInput) {
inputTypingRef.current = setTimeout(() => {
inputTypingRef.current = null;
if (props.searchOption !== "all") {
preparingElement = props.localRecipes.filter((rec) => {
return rec[props.searchOption].includes(props.searchInput);
});
} else {
preparingElement = props.localRecipes.filter((rec) => {
return rec.includes(props.searchInput);
});
}
}, 600);
}
elementRender(preparingElement);
return (
<div className={classes.RecipeCards}>{!elementsSorted ? <BeginPrompt /> : elementsSorted}</div>
);
};
Don't worry about ingredientCountSort() function. It's a working function that just rearranges the array of JSX code.
Following up to my comment in original question. elementsSorted is changed, but it doesn't trigger a re-render because there isn't a state update.
instead of
let elementsSorted
and
elementsSorted = ...
try useState
import React, { useState } from 'react'
const RecipeCards = (props) => {
....
const [ elementsSorted, setElementsSorted ] = useState();
const ingredientCountSort = () => {
...
setElementsSorted(...whatever values elementsSorted supposed to be here)
}
Reference: https://reactjs.org/docs/hooks-state.html
I used useEffect() and an additional useRef() while restructuring the order of functions
const RecipeCards = (props) => {
//const inputTypingRef = useRef(null);
let preparingElement = props.localRecipes;
let finalElement;
const [enteredFilter, setEnteredFilter] = useState(props.searchInput);
let elementsSorted;
const [elementsFiltered, setElementsFiltered] = useState();
const refTimer = useRef();
const filterActive = useRef(false);
let cardElement;
useEffect(() => {
setEnteredFilter(props.searchInput);
console.log("updating filter");
}, [props.searchInput]);
const filterRecipes = (recipes) => {
if (enteredFilter && !filterActive.current) {
console.log("begin filtering");
if (refTimer.current !== null) {
clearTimeout(refTimer.current);
}
refTimer.current = setTimeout(() => {
refTimer.current = null;
if (props.searchOption !== "all") {
setElementsFiltered(recipes.filter((rec) => {
return rec.props[props.searchOption].includes(enteredFilter);
}))
} else {
setElementsFiltered(recipes.filter((rec) => {
return rec.props.includes(enteredFilter);
}))
}
filterActive.current = true;
console.log(elementsFiltered);
}, 600);
}else if(!enteredFilter && filterActive.current){
filterActive.current = false;
setElementsFiltered();
}
finalElement = elementsFiltered ? elementsFiltered : recipes;
};
const ingredientCountSort = (recipes) => {
console.log("sorting elements");
elementsSorted = recipes.sort((a, b) => {
...
filterRecipes(elementsSorted);
};
const elementRender = (element) => {
console.log("building JSX");
cardElement = element.map((rec) => (
<RecipeCard
name={rec.name}
key={rec.id}
ingredients={rec.ingredients}
tags={rec.tags}
removeRecipe={() => props.onRemoveIngredients(rec.id)}
checkAvail={props.localIngredients}
/>
));
ingredientCountSort(cardElement);
};
//begin render /////////////////// /// /// /// /// ///
elementRender(preparingElement);
console.log(finalElement);
return (
<div className={classes.RecipeCards}>{!finalElement[0] ? <BeginPrompt /> : finalElement}</div>
);
};
There might be redundant un-optimized code I want to remove on a brush-over in the future, but it works without continuous re-renders.
I made a custom hook where I return a state. It is not printing the state value when I return an object from a custom hook, but it prints a value when I return an array which has the state as its first element.
Here is my code:
import React from "react";
import axios from "axios";
export default () => {
const [state, setState] = React.useState([]);
const fetchData = async () => {
const res = await axios.get("https://5os4e.csb.app/data.json");
setState(res.data);
};
React.useEffect(() => {
(async () => {
await fetchData();
})();
}, []);
// i am returning a object
return { state };
};
Using like this:
export default function App() {
const { st } = useTabData();
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
{JSON.stringify(st, null, 2)}
</div>
);
}
why is st value not printed? It is printed when I return an array.
https://codesandbox.io/s/sweet-meadow-9l41z?file=/src/App.js:89-276
You need to your code in couple of ways
1st way - you destruct your state
const { state:st } = useTabData();
2nd way - directly call function
{JSON.stringify(useTabData().state, null, 2)}
your passing and and accessing variable name should be same or you can use destructuring like 1st step to change variable name.
Little hint for beginners
// Return object
const myFunct1 = () => {
// Return values
const alpha = 1;
const beta = 2;
const gamma = 3;
return { alpha, beta, gamma };
}
// Return array
const myFunct2 = () => {
// Return values
const alpha = 111;
const beta = 222;
const gamma = 333;
return [alpha, beta, gamma];
}
// Get values from object return
let { alpha, beta, gamma } = myFunct1();
console.log(alpha, beta, gamma);
// Get values from array return
const [one, two, three] = myFunct2();
console.log(one, two, three);
function CountBtn1({ onClick, count }) {
console.log('CountBtn1 render')
return <button onClick={onClick}>{count}</button>
}
function CountBtn2({ onClick, count }) {
console.log('CountBtn2 render')
return <button onClick={onClick}>{count}</button>
}
function Counter() {
const [count1, setCount1] = React.useState(0)
// const increment1 = () => setCount1(c => c + 1)
const increment1 = React.useCallback(() => setCount1(c => c + 1), [])
const [count2, setCount2] = React.useState(0)
// const increment2 = () => setCount2(c => c + 1)
const increment2 = React.useCallback(() => setCount2(c => c + 1), [])
return (
<>
<CountBtn1 count={count1} onClick={increment1} />
<CountBtn2 count={count2} onClick={increment2} />
</>
)
}
when I click Button1` also render
the log is
CountBtn1 render
CountBtn2 render
My useMemo useCallback does not reduce the number of render. What is the correct way to use useMemo and useCallback?
Wrap your components with a memo:
const CountBtn1 = React.memo(function CountBtn1({ onClick, count }) {
console.log('CountBtn1 render')
return <button onClick={onClick}>{count}</button>
})
const CountBtn2 = React.memo(function CountBtn2({ onClick, count }) {
console.log('CountBtn2 render')
return <button onClick={onClick}>{count}</button>
})
I have two calls to two seperate APIs I want to create a third result that is a combination of the first two. However I cannot access the variables.
export default function AutoServices() {
const [data, setData] = useState();
const [a, setA] = useState();
const [b, setB] = useState();
const [C, setC] = useState();
useEffect(() => {
FetchCurrentPage(result => setData(result.content.default));
FetchAutoServices(result => b(result.records));
FetchSetAccel("/locationsinit", result => setA(result.serviceGroups));
setC(AddDesc(a, b));
}, []);
function AddDesc(x, y) {
var result = x;
for (var i = 0; i < result.length; i++) {
for(var j = 0; j < y.length; j++)
{
if(result[i].slug === y[j].iconclass)
{
Object.assign(result[i], {Desc: y.content.default.teaser});
}
}
}
return result;
}
return (
<>
{data && (
<Helmet> ...........
The error in the browser is
Cannot read property 'length' of undefined referring to the first line of the for statement
I know that my code is returning json because when I inspect chrome I can see the json
State updates are asynchronous, which is why a is still undefined when you call AddDesc in the same useEffect closure. You could add a second useEffect with a and b as dependencies:
useEffect(() => {
FetchCurrentPage(({ content }) => setData(content.default));
FetchAutoServices(({ records }) => setB(records));
FetchSetAccel("/locationsinit", ({ serviceGroups }) => setA(serviceGroups));
}, []);
useEffect(() => {
if (a && b) setC(AddDesc(a, b));
}, [a, b]);
You are dealing with async code, so you'll have to wait on the first two to resolve. You can use Promise.all, and then do whatever with the result *potentially like:
useEffect(() => {
FetchCurrentPage(result => result.content.default)
const [a, b] = Promise.all([
FetchAutoServices(result => result.records),
FetchSetAccel("/locationsinit", result => result.serviceGroups);
]);
setC(AddDesc(a, b));},
[]);