React Native not rerendering after state change - reactjs

I realize that this is a common problem but I haven't found a solution that doesn't feel like it's just covering up the real problem. I have code in a component that is not being updated when the state changes. I'm pretty sure that the state is changing because when I save my code while the simulator is on the screen, it updates, but it doesn't updating when going to that screen normally. Also, in the code segment below there are two very similar functions. The first one works and renders properly. The second one works but doesn't rerender after updating.
fetchUsername().then((uname) => {
setUsername(uname);
return uname;
}).then((uname) =>
(fetchPaymentsInChat(props.threadID)
.then((payments) => {
return Promise.all(
payments.map(pid => {
return fetchPayment(pid)
.then(paymentDetails => {
return paymentDetails.sender === uname ? paymentDetails.value * paymentDetails.users.length : 0
})
})
);
}).then((paymentValues) => {
const paymentSum = paymentValues.reduce((acc, val) => parseInt(acc) + parseInt(val), 0);
setAmmountReceive(paymentSum);
}).catch(err => {
alert(err);
}),
fetchPaymentsInChat(props.threadID)
.then((payments) => {
return Promise.all(
payments.map(pid => {
return fetchPayment(pid)
.then(paymentDetails => {
return paymentDetails.users.includes(username) ? paymentDetails.value : 0
})
})
);
}).then((paymentValues) => {
const paymentSum = paymentValues.reduce((acc, val) => parseInt(acc) + parseInt(val), 0);
setAmmountOwe(paymentSum);
}).catch(err => {
alert(err);
}))

Related

display live shell output in react dom using electron ipc

I can only access ipcRenderer in preload.js ( i disabled nodeIntegration ) so how do i display the output line by line whenever i get the output in preload.js
main.js
function execShellCommands(commands) {
let shellProcess = spawn("powershell.exe", [commands[0]])
shellProcess.stdout.on("data", (data) => {
mainWindow.webContents.send("sendToRenderer/shell-output", data.toString())
})
shellProcess.stderr.on("data", (data) => {
mainWindow.webContents.send("sendToRenderer/shell-output", "stderr: " + data.toString())
})
shellProcess.on("exit", () => {
mainWindow.webContents.send("sendToRenderer/shell-output", "shell-exited")
commands.shift()
if (0 < commands.length) {
execShellCommands(commands)
}
})
}
ipcMain.on("sendToElectron/execShellCommands", (event, args) => {
execShellCommands(args)
})
preload.js
let API = {
execShellCommands: (action) => ipcRenderer.send("sendToElectron/execShellCommands", action)
}
contextBridge.exposeInMainWorld("ElectronAPI", API)
ipcRenderer.on("sendToRenderer/shell-output", (event, output) => {
console.log(output)
})
react's App.jsx
ElectronAPI.execShellCommands(["spicetify apply"])
The output in printed one by one in the console but how do i display the output in react DOM (App.jsx) one by one in a p tag?
First, you need to expose the shell-output listener in you API so your components can access it:
shellOutput: (callback) => {
const channel = "sendToRenderer/shell-output";
const subscription = (_event, output) => callback(output);
ipcRenderer.on(channel , subscription);
return () => {
ipcRenderer.removeListener(channel, subscription);
};
}
On your components, you can now access this function with window.ElectronAPI.shellOutput. To log the outputs, you can create a state to store them, and set the listener with a useEffect():
const [outputs, setOutputs] = useState([]);
useEffect(() => {
const removeOutputListener = window.ElectronAPI.shellOutput(output => {
setOutputs(previousOutputs => [
...previousOutputs,
output
]);
});
return removeOutputListener;
}, []);
return (
<div>
{outputs.map((output, i) => (
<p key={i}>{output}</p>
))}
</div>
);

Cannot setstate in nested axios post request in react

I am trying to access the res.data.id from a nested axios.post call and assign it to 'activeId' variable. I am calling the handleSaveAll() function on a button Click event. When the button is clicked, When I console the 'res.data.Id', its returning the value properly, but when I console the 'activeId', it's returning null, which means the 'res.data.id' cannot be assigned. Does anyone have a solution? Thanks in advance
const [activeId, setActiveId] = useState(null);
useEffect(() => {}, [activeId]);
const save1 = () => {
axios.get(api1, getDefaultHeaders())
.then(() => {
const data = {item1: item1,};
axios.post(api2, data, getDefaultHeaders()).then((res) => {
setActiveId(res.data.id);
console.log(res.data.id); // result: e.g. 10
});
});
};
const save2 = () => {
console.log(activeId); // result: null
};
const handleSaveAll = () => {
save1();
save2();
console.log(activeId); // result: again its still null
};
return (
<button type='submit' onClick={handleSaveAll}>Save</button>
);
Setting the state in React acts like an async function.
Meaning that the when you set the state and put a console.log right after it, like in your example, the console.log function runs before the state has actually finished updating.
Which is why we have useEffect, a built-in React hook that activates a callback when one of it's dependencies have changed.
Example:
useEffect(() => {
console.log(activeId);
}, [activeId);
The callback will run every time the state value changes and only after it has finished changing and a render has occurred.
Edit:
Based on the discussion in the comments.
const handleSaveSections = () => {
// ... Your logic with the `setState` at the end.
}
useEffect(() => {
if (activeId === null) {
return;
}
save2(); // ( or any other function / logic you need )
}, [activeId]);
return (
<button onClick={handleSaveSections}>Click me!</button>
)
As the setState is a async task, you will not see the changes directly.
If you want to see the changes after the axios call, you can use the following code :
axios.post(api2, data, getDefaultHeaders())
.then((res) => {
setActiveId(res.data.id)
console.log(res.data.id) // result: e.g. 10
setTimeout(()=>console.log(activeId),0);
})
useEffect(() => {
}, [activeId]);
const [activeId, setActiveId] = useState(null);
const save1 = () => {
const handleSaveSections = async () => {
activeMetric &&
axios.get(api1, getDefaultHeaders()).then(res => {
if (res.data.length > 0) {
Swal.fire({
text: 'Record already exists',
icon: 'error',
});
return false;
}
else {
const data = {
item1: item1,
item2: item2
}
axios.post(api2, data, getDefaultHeaders())
.then((res) => {
setActiveId(res.data.id)
console.log(res.data.id) // result: e.g. 10
})
}
});
}
handleSaveSections()
}
const save2 = () => {
console.log(activeId); //correct result would be shown here
}
const handleSaveAll = () => {
save1();
save2();
}
return (
<button type="submit" onClick={handleSaveAll}>Save</button>
)

React does not re-render updated map, even with different keys

May be it's a stupid issue, but I can't seem to see it.
I have a file input which uploads an image, and I receive from API multiple images resized into different dimensions.
Before the images are loaded, I don't have a 'file' property in the image object.
After they are loaded - I have the file property.
The problem is, when the resized images are loaded, the map still renders 'no-component'. Even when the key is changed...
Here is the code with some console debugging for more clarity:
{newsImages.map(image => {
console.log('Has file?', image.hasOwnProperty('file'))
const imageComponent = image.hasOwnProperty('file')
? 'has component' // (<AuthorizedImage fileId={image.file} />)
: 'no component';
console.log('Image component: ', imageComponent);
const key = image.label + (image.hasOwnProperty('file') ? image.file : '-');
console.log('Key: ', key);
return (
<div key={key}>
<FormUploadFile
label={`Image for ${image.label}`}
placeholder={"Choose image"}
onChange={value => console.log(value)}
/>
{imageComponent}
<br />
{key}
</div>
)
})}
So far so good. But the web page itself does not show the changes like you can see below:
EDIT: More details on the component. Here is how the images are processed:
const [newsImages, setNewsImages] = useState([]);
useEffect(() => {
Api.NewsImageSizes.List()
.then(response => {
let newsImages = [];
response['hydra:member'].forEach(size => {
newsImages.push({label: size.label})
})
setNewsImages(newsImages.reverse());
})
.catch(ErrorHandling.GlobalStateError);
}, []);
Then when a file is uploaded...
const generateNewsImages = (file) => {
Api.Files.Upload(file)
.then(response => {
Api.News.GenerateImages(response.id)
.then(response => {
response.images.forEach(image => {
newsImages.forEach((element, index) => {
if (element.label === image.label) {
element.file = image.file;
newsImages[index] = element;
}
})
})
console.log('Setting news images...', newsImages);
setNewsImages(newsImages);
})
.catch(ErrorHandling.GlobalStateError);
})
.catch(ErrorHandling.GlobalStateError);
}
And the console again:
Looks like you are mutating the state in the generateNewsImages function.
.then(response => {
response.images.forEach(image => {
newsImages.forEach((element, index) => { // <-- newsImages is state reference
if (element.label === image.label) {
element.file = image.file; // <-- mutation!!
newsImages[index] = element;
}
})
})
console.log('Setting news images...', newsImages);
setNewsImages(newsImages); // <-- save same reference back into state
})
When updating React state you must create shallow copies of all state (and nested state) that you are updating.
.then(response => {
const nextNewsImages = newsImages.slice(); // <-- shallow copy state
response.images.forEach(image => {
nextNewsImages.forEach((element, index) => {
if (element.label === image.label) {
nextNewsImages[index] = { // <-- new object reference
...nextNewsImages[index], // <-- shallow copy
file: image.file, // <-- update property
};
}
})
})
console.log('Setting news images...', nextNewsImages);
setNewsImages(nextNewsImages); // <-- save new
})

setSomeState() called from a catch() function doesn't update

If have a problem with react code where, for some reason, everything works as expected when I update the UI from the MyApp.promise().then(<here>) but not in my MyApp.promise().then().catch(<here>)
I know the code is actually executed up to the point I actually call setData which is my useState() returned function
A call to that function in then() works just fine, not in catch()
the exception that eventually triggers catch() works fine since the catch() is executed as expected
I added a console.log() inside my component, and I see that it's no longer re-drawn when the updates comes from catch()
I guess my question is : what would be special in a catch() function so react wouldn't behave ?
This is the code for my application hook that handles upgrade status updates :
const useUpdateStatus = () => {
const [data,setData] = useState({status: STATUS.IDLE,changelog:null,tasks:[]})
const updateData = (d) => {
// We call setData with an anonymous function so we can merge previous
// data with new data
setData((prev) => {
console.log({ ...prev, ...d })
return { ...prev, ...d }
})
};
// Only once, we set the timer to periodically update status
useEffect(() => {
setInterval(() => {
MyApp.get('/system/upgrade')
.then((upgrade) => {
// If anything is not "pending", it means we are upgrading
for (var t of upgrade.tasks) {
if (t.status !== "pending") {
updateData({ status: STATUS.INSTALLING})
}
}
// updateData will call setData with the full status
// This works as intended, UI is updated on each step
updateData({ tasks: upgrade.tasks, changelog: upgrade.changelog})
})
.catch((e) => {
// If data can't be fetched, it probably means we are restarting
// services, so we updated the tasks array accordingly
setData((prev) => {
for (var t of prev.tasks) {
if (t['id'] === "restarting") {
t['status'] = 'running'
}
else if (t['status'] == "running") {
t['status'] = 'finished'
}
}
// The expected data is logged here
console.log(prev)
return prev
})
})
}, 1000);
},[])
return data
}
This is the presentation layer :
// Using the hook :
const { changelog, tasks, status } = useUpdateStatus()
// Somewhere int he page :
<UpdateProgress tasks={tasks}/>
// The actual components :
const UpdateProgress = (props) => {
return(
<div style={{display: "flex", width: "100%"}}>
{ props.tasks.map(s => {
return(
<UpdateTask key={s.name} task={s}/>
)
})}
</div>
)
}
const UpdateTask = (props) => {
const colors = {
"pending":"LightGray",
"running":"SteelBlue",
"finished":"Green",
"failed":"red"
}
return(
<div style={{ textAlign: "center", flex: "1" }}>
<Check fill={colors[props.task.status]} width="50px" height="50px"/><br/>
<p style={props.task.status==="running" ? {fontWeight: 'bold'} : { fontWeight: 'normal'}}>{props.task.name}</p>
</div>
)
}
React performs an Object.is comparison to check is a re-render is needed after a state update call. Since you are mutating the state in catch block, react is falsely notified that the state hasn't changed and hence a re-render in not triggered
You can update your state like below to make it work
.catch((e) => {
// If data can't be fetched, it probably means we are restarting
// services, so we updated the tasks array accordingly
setData((prev) => {
for (var t of prev.tasks) {
if (t['id'] === "restarting") {
t['status'] = 'running'
}
else if (t['status'] == "running") {
t['status'] = 'finished'
}
}
// The expected data is logged here
console.log(prev)
return {...prev}
})
})
However a better way to update state is to do it in an immutable manner
.catch((e) => {
// If data can't be fetched, it probably means we are restarting
// services, so we updated the tasks array accordingly
setData((prev) => ({
...prev,
tasks: prev.tasks.map((task) => {
if (task.id === "restarting") {
return { ...task, status: 'running'}
}
else if (task.id === "running") {
return { ...task, status: 'finished'}
}
return task
})
}))
})

Multiple state changes in event listener, how to NOT batch the DOM updates?

I'm building a component to test the performance of different algorithms. The algorithms return the ms they took to run and this is want I want to display. The "fastAlgorithm" takes about half a second, and the "slowAlgorithm" takes around 5 seconds.
My problem is that the UI is not re-rendered with the result until both algorithms have finished. I would like to display the result for the fast algorithm as soon as it finishes, and the slow algorithm when that one finishes.
I've read about how React batches updates before re-rendering, but is there someway to change this behavior? Or is there a better way to organize my component/s to achieve what I want?
I'm using react 16.13.1
Here is my component:
import { useState } from 'react'
import { fastAlgorithm, slowAlgorithm } from '../utils/algorithms'
const PerformanceTest = () => {
const [slowResult, setSlowResult] = useState(false)
const [fastResult, setFastResult] = useState(false)
const testPerformance = async () => {
fastAlgorithm().then(result => {
setFastResult(result)
})
slowAlgorithm().then(result => {
setSlowResult(result)
})
}
return (
<div>
<button onClick={testPerformance}>Run test!</button>
<div>{fastResult}</div>
<div>{slowResult}</div>
</div>
)
}
export default PerformanceTest
I read somewhere that ReactDOM.flushSync() would trigger the re-rendering on each state change, but it did not make any difference. This is what I tried:
const testPerformance = async () => {
ReactDOM.flushSync(() =>
fastAlgorithm().then(result => {
setFastResult(result)
})
)
ReactDOM.flushSync(() =>
slowAlgorithm().then(result => {
setSlowResult(result)
})
)
}
And also this:
const testPerformance = async () => {
fastAlgorithm().then(result => {
ReactDOM.flushSync(() =>
setFastResult(result)
)
})
slowAlgorithm().then(result => {
ReactDOM.flushSync(() =>
setSlowResult(result)
)
})
}
I also tried restructuring the algorithms so they didn't use Promises and tried this, with no luck:
const testPerformance = () => {
setFastResult(fastAlgorithm())
setSlowResult(slowAlgorithm())
}
Edit
As Sujoy Saha suggested in a comment below, I replaced my algorithms with simple ones using setTimeout(), and everything works as expected. "Fast" is displayed first and then two seconds later "Slow" is displayed.
However, if I do something like the code below it doesn't work. Both "Fast" and "Slow" shows up when the slower function finishes... Does anyone know exactly when/how the batch rendering in React happens, and how to avoid it?
export const slowAlgorithm = () => {
return new Promise((resolve, reject) => {
const array = []
for(let i = 0; i < 9000; i++) {
for(let y = 0; y < 9000; y++) {
array.push(y);
}
}
resolve('slow')
})
}
Your initial PerfomanceTest component is correct. The component will re-render for the each state change. I think issue is in your algorithm. Please let us know how did you returned promise there.
Follow below code snippet for your reference.
export const fastAlgorithm = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('fast')
}, 1000)
})
}
export const slowAlgorithm = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('slow')
}, 3000)
})
}
Are you running your algorithms synchronously on the main thread? If so, that's probably what's blocking React from re-rendering. You may need to move them to worker threads.
The below is loosely based on this answer, minus all the compatibility stuff (assuming you don't need IE support):
// `args` must contain all dependencies for the function.
const asyncify = (fn) => {
return (...args) => {
const workerStr =
`const fn = ${fn.toString()}
self.onmessage = ({ data: args }) => {
self.postMessage(fn(...args))
}`
const blob = new Blob([workerStr], { type: 'application/javascript' })
const worker = new Worker(URL.createObjectURL(blob))
let abort = () => {}
const promise = new Promise((resolve, reject) => {
worker.onmessage = (result) => {
resolve(result.data)
worker.terminate()
}
worker.onerror = (err) => {
reject(err)
worker.terminate()
}
// In case we need it for cleanup later.
// Provide either a default value to resolve to
// or an Error object to throw
abort = (value) => {
if (value instanceof Error) reject(value)
else resolve(value)
worker.terminate()
}
})
worker.postMessage(args)
return Object.assign(promise, { abort })
}
}
const multiplySlowly = (x, y) => {
const start = Date.now()
const arr = [...new Array(x)].fill([...new Array(y)])
return {
x,
y,
result: arr.flat().length,
timeElapsed: Date.now() - start,
}
}
const multiplySlowlyAsync = asyncify(multiplySlowly)
// rendering not blocked - just pretend this is React
const render = (x) => document.write(`<pre>${JSON.stringify(x, null, 4)}</pre>`)
multiplySlowlyAsync(999, 9999).then(render)
multiplySlowlyAsync(15, 25).then(render)
Note that fn is effectively being evaled in the context of the worker thread here, so you need to make sure the code is trusted. Presumably it is, given that you're already happy to run it on the main thread.
For completeness, here's a TypeScript version:
type AbortFn<T> = (value: T | Error) => void
export type AbortablePromise<T> = Promise<T> & {
abort: AbortFn<T>
}
// `args` must contain all dependencies for the function.
export const asyncify = <T extends (...args: any[]) => any>(fn: T) => {
return (...args: Parameters<T>) => {
const workerStr =
`const fn = ${fn.toString()}
self.onmessage = ({ data: args }) => {
self.postMessage(fn(...args))
}`
const blob = new Blob([workerStr], { type: 'application/javascript' })
const worker = new Worker(URL.createObjectURL(blob))
let abort = (() => {}) as AbortFn<ReturnType<T>>
const promise = new Promise<ReturnType<T>>((resolve, reject) => {
worker.onmessage = (result) => {
resolve(result.data)
worker.terminate()
}
worker.onerror = (err) => {
reject(err)
worker.terminate()
}
// In case we need it for cleanup later.
// Provide either a default value to resolve to
// or an Error object to throw
abort = (value: ReturnType<T> | Error) => {
if (value instanceof Error) reject(value)
else resolve(value)
worker.terminate()
}
})
worker.postMessage(args)
return Object.assign(promise, { abort }) as AbortablePromise<
ReturnType<T>
>
}
}

Resources