Update object data-members in React state doesn't trigger re-rendering
I'm using TypeScript and React to try to implement my custom types in React component. However the component is not re-rendering if data members of object in state has updated.
Here's my code:
class Person {
name: string;
surname: string;
constructor(name: string, surname: string) {
this.name = name;
this.surname = surname;
}
}
class App extends Component<AppProps, AppState> {
constructor(props) {
super(props);
this.state = {
persons: [new Person("Alpha", "A"), new Person("Bravo", "B")]
};
}
render() {
return (
<div>
<button
onClick={() => {
this.state.persons[0].name = "Zulu";
this.state.persons[0].surname = "Z";
}}
>
Change A to Z
</button>
<button
onClick={() => {
this.setState({
persons: [...this.state.persons, new Person("Charlie", "C")]
});
}}
>
Add C
</button>
<button onClick={() => { this.forceUpdate(); }}> Force Update </button>
{this.state.persons.map(person => (
<p>
{person.name} {person.surname}
</p>
))}
</div>
);
}
}
If I click on Change A to Z
nothing will change - unless Add C
or Force Update
is clicked. I am assuming React cannot detect changes made in Person
therefore no re-render.
I have some questions about this problem.
- Is this approach (custom datatype as state) recommended to use in React
- If yes - how do I make this work? (React able to detect changes made in
Person
) - If no - what is the recommended approach to use custom datatype in React?
- If yes - how do I make this work? (React able to detect changes made in
1 answer
-
answered 2021-01-11 05:15
CertainPerformance
React will not re-render by default if every element in the new state is
===
to every element in the old state. You also have to callsetState
in order to trigger a re-render. YourChange A to Z
is failing on both counts - you need to both create a newpersons
array without mutating what's currently in state, and then callsetState
with the new array.Also, having a
Person
instance doesn't look to be accomplishing anything, since there are no methods on the class - and it'll make changing the state harder, so I'd removePerson
entirely. Tryconstructor(props) { super(props); this.state = { persons: [{ name: 'Alpha', surname: 'A' }, { name: 'Bravo', surname: 'B' }] }; }
Then change
onClick={() => { this.state.persons[0].name = "Zulu"; this.state.persons[0].surname = "Z"; }}
to
onClick={() => { this.setState({ persons: [{ name: 'Zulu', surname: 'Z' }, ...this.state.persons.slice(1)] }); }}
The
persons: [{ name: 'Zulu', surname: 'Z' }, ...this.state.persons.slice(1)]
effectively immutably replaces the first element of the array with the new
Zulu
object.
See also questions close to this topic
-
NodeJs reading PDF file from disk
I have a PDF file which I want to read into memory using NodeJS. Ideally I'd like to encode it using
base64
for transferring it. But somehow theread
function does not seem to read the full PDF file, which makes no sense to me.The original file
test.pdf
has 90kB on disk. But if I read and write it back to disk there are just 82kB and the new PDFtest-out.pdf
is not ok. The pdf viewer says:Unable to open document. The pdf document is damaged.
The base64 encoding therefore also does not work correctly. I tested it using this webservice. Does someone know why and what is happening here?
I found this post already.
fs = require('fs'); let buf = fs.readFileSync('test.pdf'); // returns raw buffer binary data // buf = fs.readFileSync('test.pdf', {encoding:'base64'}); // for the base64 encoded data // ...transfer the base64 data... fs.writeFileSync('test-out.pdf', buf, 'base64); // should be pdf again
-
Mark coordinates of 3 different jsons in Mapbox
I am collecting address information from a database, so I am putting them into a Json and then taking out their coordinates and marking them, there are 3 types of addresses (institutions, students, supervisors) so for each type of address I am using a different marker the problem arises that it only marks the geojson coordinates, but those of geojson2 and geojson3 do not. Any suggestion?
html:
<!DOCTYPE html> <html> <head> <script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.49.0/mapbox-gl.js'></script> <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.49.0/mapbox-gl.css' rel='stylesheet' /> <style> body { margin: 0; padding: 0; } #map { position: absolute; top: 0; bottom: 0; width: 100%; } </style> <script> var json1=[]; var json2=[]; var json3=[]; </script> {% block content %} {% if instituciones %} {% for direccion in instituciones %} <script> json1.push( "{{direccion.ins_direccion}}, {{direccion.ins_comuna}}, {{direccion.ins_provicincia}}, chile" ,) console.log("instituciones"); console.log(json1); </script> {% endfor %} {% endif %} {% if profesores %} {% for direccion in profesores %} <script> json2.push( "{{direccion.prs_direccion}}, {{direccion.prs_comuna}}, chile" ,) console.log("profesores"); console.log(json2); </script> {% endfor %} {% endif %} {% if estudiantes %} {% for direccion in estudiantes %} <script> json3.push( "{{direccion.est_direccion}}, {{direccion.est_comuna}}, chile" ,) console.log("estudiantes"); console.log(json3); </script> {% endfor %} {% endif %} {% endblock %} </head> <body style="word-wrap:break-word;"> <div id='map'></div> <script src='https://unpkg.com/mapbox@1.0.0-beta9/dist/mapbox-sdk.min.js'></script> <script> mapboxgl.accessToken = 'pk.eyJ1IjoibGF3aXgxMCIsImEiOiJjamJlOGE1bmcyZ2V5MzNtcmlyaWRzcDZlIn0.ZRQ73zzVxwcADIPvsqB6mg'; console.log(mapboxgl.accessToken); var client = new MapboxClient(mapboxgl.accessToken); console.log(client); // var geojson = { 'type': 'FeatureCollection', 'features': [ ] }; for(var i = 0; i < json1.length; i++) { var address= json1[i]; console.log(address); var test = client.geocodeForward(JSON.stringify(address), function(err, data, res) { // data is the geocoding result as parsed JSON // res is the http response, including: status, headers and entity properties console.log(res); console.log(res.url); console.log(data); var coordinates = data.features[0].center; geojson.features.push({ 'type': 'Feature', 'properties': { 'message': JSON.stringify(address), 'iconSize': [60, 60] }, 'geometry': { 'type': 'Point', 'coordinates': [coordinates[0], coordinates[1]] } },) console.log("JSON COORDENADAS 1") console.log(JSON.stringify(geojson.features)) console.log(geojson.features.geometry) var map = new mapboxgl.Map({ container: 'map', style: 'mapbox://styles/mapbox/streets-v9', center: coordinates, zoom: 10 }); // var longt : coordinates[0].toString(); // add markers to map geojson.features.forEach(function (marker) { // create a DOM element for the marker var el = document.createElement('div'); el.className = 'marker'; el.style.backgroundImage = 'url(https://farm9.staticflickr.com/8604/15769066303_3e4dcce464_n.jpg)' el.style.width = marker.properties.iconSize[0] + 'px'; el.style.height = marker.properties.iconSize[1] + 'px'; el.addEventListener('click', function () { window.alert(marker.properties.message); }); // add marker to map new mapboxgl.Marker(el) .setLngLat(marker.geometry.coordinates) .addTo(map); }); }); } mapboxgl.accessToken = 'pk.eyJ1IjoibGF3aXgxMCIsImEiOiJjamJlOGE1bmcyZ2V5MzNtcmlyaWRzcDZlIn0.ZRQ73zzVxwcADIPvsqB6mg'; console.log(mapboxgl.accessToken); var client = new MapboxClient(mapboxgl.accessToken); console.log(client); // var geojson2 = { 'type': 'FeatureCollection', 'features': [ ] }; for(var i = 0; i < json2.length; i++) { var address= json2[i]; console.log(address); var test = client.geocodeForward(JSON.stringify(address), function(err, data, res) { // data is the geocoding result as parsed JSON // res is the http response, including: status, headers and entity properties console.log(res); console.log(res.url); console.log(data); var coordinates = data.features[0].center; geojson2.features.push({ 'type': 'Feature', 'properties': { 'message': JSON.stringify(address), 'iconSize': [60, 60] }, 'geometry': { 'type': 'Point', 'coordinates': [coordinates[0], coordinates[1]] } },) console.log("JSON2 COORDENADAS") console.log(JSON.stringify(geojson2.features)) console.log(geojson2.features.geometry) // var longt : coordinates[0].toString(); // add markers to map geojson2.features.forEach(function (marker) { // create a DOM element for the marker var el = document.createElement('div'); el.className = 'marker'; el.style.backgroundImage = 'url(https://farm9.staticflickr.com/8604/15769066303_3e4dcce464_n.jpg)'; el.style.width = marker.properties.iconSize[0] + 'px'; el.style.height = marker.properties.iconSize[1] + 'px'; el.addEventListener('click', function () { window.alert(marker.properties.message); }); // add marker to map new mapboxgl.Marker(el) .setLngLat(marker.geometry.coordinates) .addTo(map); }); }); } console.log(mapboxgl.accessToken); var client3 = new MapboxClient(mapboxgl.accessToken); console.log(client); // var geojson3 = { 'type': 'FeatureCollection', 'features': [ ] }; for(var i = 0; i < json3.length; i++) { var address= json3[i]; console.log(address); var test3 = client.geocodeForward(JSON.stringify(address), function(err, data, res) { // data is the geocoding result as parsed JSON // res is the http response, including: status, headers and entity properties console.log(res); console.log(res.url); console.log(data); var coordinates = data.features[0].center; geojson3.features.push({ 'type': 'Feature', 'properties': { 'message': JSON.stringify(address), 'iconSize': [60, 60] }, 'geometry': { 'type': 'Point', 'coordinates': [coordinates[0], coordinates[1]] } },) console.log(JSON.stringify("JSON COORDENADAS")) console.log(JSON.stringify(geojson3.features)) console.log(geojson3.features.geometry) // var longt : coordinates[0].toString(); // add markers to map geojson3.features.forEach(function (marker) { // create a DOM element for the marker var el3 = document.createElement('div'); el3.className = 'marker'; el3.style.backgroundImage = 'url(https://farm9.staticflickr.com/8604/15769066303_3e4dcce464_n.jpg)'; el3.style.width = marker.properties.iconSize[0] + 'px'; el3.style.height = marker.properties.iconSize[1] + 'px'; el3.addEventListener('click', function () { window.alert(marker.properties.message); }); // add marker to map new mapboxgl.Marker(el3) .setLngLat(marker.geometry.coordinates) .addTo(map); }); }); } </script> </body> </html>
-
Unable to define a variable error - html,css,js
I am facing a bit of difficulty with defining the
MotionPathPlugin
as it saysvariable not defined
Before, it said
gsap not defined
and I managed to fix that by adding<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.6.0/gsap.min.js"></script>
However, I am unable to fix this error:
The code works perfectly fine on codepen but it is not working on
repl.it
for some reason. I will paste my code of the animation here too, in case there is a particular reference I may be missing. I added that particular line to avoid the errorgsap not defined
but I cannot seem to figure out the solution to this error. Any suggestions?The error occurs here on StackOverFlow too:
gsap.registerPlugin(MotionPathPlugin); const tween = gsap.timeline(); tween.to(".paper-plane", { duration: 1, ease: "power1.inOut", motionPath: { path: [ {x: 100, y: 0}, {x: 300, y: 10}, {x: 500, y: 100}, {x: 750, y: -100}, {x: 350, y: -50}, {x: 600, y: 100}, {x: 800, y: 0}, {x: window.innerWidth, y: -250} ], curviness: 2, autoRotate: true } }); const controller = new ScrollMagic.Controller(); const scene = new ScrollMagic.Scene({ triggerElement: '.animation', duration: 1000, triggerHook: 0 }) .setTween(tween) .setPin('.animation') .addTo(controller);
*{ margin: 0; padding: 0; box-sizing: border-box; } header, footer{ height: 100vh; display: flex; justify-content: center; align-items: center; font-family: "Montserrat", sans-serif; } header h1{ font-size: 60px; } .animation{ height: 100vh; background-image: linear-gradient(to top, #fbc2eb 0%, #a6c1ee 100%); position: relative; overflow: hidden; } .paper-plane{ position: absolute; top: 50%; left: 0%; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.6.0/gsap.min.js"></script> <div class="animation"> <img class="paper-plane" src="https://i.postimg.cc/W1w9dT1x/paper.png" alt="Paper Plane"> </div>
-
Module not found: Error: Can't resolve '@emotion/styled/base' when running story book
I recently installed Storybook to my project
Dependencies and Dev Dependencies below:
"dependencies": { "@emotion/react": "^11.1.4", "@emotion/styled": "^11.0.0", "@fortawesome/fontawesome-svg-core": "^1.2.34", "@fortawesome/free-solid-svg-icons": "^5.15.2", "@fortawesome/react-fontawesome": "^0.1.14", "@hot-loader/react-dom": "^17.0.1", "node-sass": "^5.0.0", "react": "^17.0.1", "react-content-loader": "^6.0.1", "react-dom": "^17.0.1", "react-hot-loader": "^4.13.0", "react-router-dom": "^5.2.0" }, "devDependencies": { "@babel/core": "^7.12.10", "@babel/preset-env": "^7.12.11", "@babel/preset-react": "^7.12.10", "@commitlint/cli": "^11.0.0", "@commitlint/config-conventional": "^11.0.0", "@emotion/babel-plugin": "^11.1.2", "@emotion/babel-preset-css-prop": "^11.0.0", "@emotion/jest": "^11.1.0", "@emotion/styled-base": "^11.0.0", "@storybook/addon-actions": "^6.1.15", "@storybook/addon-essentials": "^6.1.15", "@storybook/addon-links": "^6.1.15", "@storybook/preset-scss": "^1.0.3", "@storybook/react": "^6.1.15", "@wojtekmaj/enzyme-adapter-react-17": "^0.4.1", "babel-eslint": "^10.1.0", "babel-jest": "^26.6.3", "babel-loader": "^8.2.2", "babel-plugin-emotion": "^11.0.0", "babel-plugin-require-context-hook": "^1.0.0", "clean-webpack-plugin": "^3.0.0", "css-loader": "^5.0.1", "enzyme": "^3.11.0", "enzyme-to-json": "^3.6.1", "eslint": "^7.18.0", "eslint-config-prettier": "^7.1.0", "eslint-loader": "^4.0.2", "eslint-plugin-import": "^2.22.1", "eslint-plugin-jest": "^24.1.3", "eslint-plugin-react": "^7.22.0", "eslint-plugin-react-hooks": "^4.2.0", "html-webpack-plugin": "^5.0.0-alpha.3", "husky": "^4.3.8", "jest": "^26.6.3", "jest-prop-type-error": "^1.1.0", "prettier": "^2.2.1", "react-test-renderer": "^17.0.1", "sass": "^1.32.4", "sass-loader": "^10.1.1", "standard-version": "^9.1.0", "style-loader": "^2.0.0", "terser-webpack-plugin": "^5.1.1", "url-loader": "^4.1.1", "webpack": "^5.15.0", "webpack-bundle-analyzer": "^4.3.0", "webpack-cli": "^4.3.1", "webpack-dev-server": "^3.11.2" },
I built a simple button component that uses
@emotion/styled
for styling. I would like to add a story for this button, however, when runningnpm run storybook
i get the following errorModule not found: Error: Can't resolve '@emotion/styled/base' in '/directory/to/Button'
This is what I am importing inside my button component:
import styled from '@emotion/styled'; import { useTheme } from '@emotion/react'; const StyledButton = styled.button` cursor: pointer; font-size: ${({ fontSize }) => fontSize}; `;
This is happening to other components that use
@emotion/styled
as well. Am i missing an extra dependency or do I need to add any presets to .babelrc file?.babelrc:
{ "presets": [ "@babel/preset-env", "@babel/preset-react", "@emotion/babel-preset-css-prop" ], "env": { "test": { "plugins": ["require-context-hook"] } }, "plugins": ["react-hot-loader/babel", "@emotion"] }
-
No code when using my own search command "npm run swizzle @docusaurus/theme-classic SearchBar"
I am new to Docusaurus and I am trying to use my own search as given in the documentation using "npm run swizzle @docusaurus/theme-classic SearchBar " command. After running the command, I do not see any code in the SearchBar.js file. I saw in an example that after running the command, there were bunch of lines of code which we can modify to add functionality in the project but when I run the command, I get just a single line of code. Can anyone let me know what I am doing wrong ?
-
How to fetch a data without using useffect or setTimeout and add loader in react hooks
DETAIL*
const Detail = (props) { const { getLatest, getAll } = useRoom(); const [ rowData, setRowData ] = useState([]); const [ state, setState ] = useState([]); useEffect(() => { const fetchData = async () => { getLatest(PARAMS).then((res) => setState(res['data'].data)); getAll({length: 9999}).then((res) => setRowData(res['data'].data)); } fetchData(); }, []); return ( {state && state.map((res, i) => ( <div key={i} className="w-full px-2 flex rounded justify-center items-center p-2 m-1 bg-white"> <Room item={res} /> </div> ))} ) } export default Detail;
What I'm trying to do here is to add a loader also my problem is when I didn't use the setTimeout I'm getting error which is
Request failed with status code 500
. but when I added the setTimeout there's no error.setTimeout(() => {...fetchData }
How to fetch a data without using the setTimeout?
-
google cloud function error DEADLINE EXCEEDED
I'm trying to write a simple pub/sub triggered cloud function to update my Firestore collection of stocks.I'm getting a bunch of weird error messages with one prominent of Error: 4 DEADLINE_EXCEEDED: Deadline exceeded. Whats even more strange that some stocks gets updated correctly while others don't. Im new to Javascript/Typescript so obviously I have some misunderstanding about how to return promises in this case here. The idea here is very simple loop through each ticker in the collection make a request to updated data then update existing document data and save it
export const updateChart = functions.pubsub.schedule('35 16 * * 1-5').timeZone('America/New_York').onRun(async(_context) => { const key = functions.config().services.key as string const db = admin.firestore() const charts5DRef = db.collection("charts5D") var needsUpdate : boolean = true // I cut off some unrelated code for brevity sake if (needsUpdate){ const snapshot = await charts5DRef.get() var path = `` const timestamp = Date.now() / 1000 return snapshot.forEach(async function(document){ // this could contain 100's of stock tickers const ticker = document.id const docData = document.data() var labels = docData.labels as [string] var marketNotional = docData.marketNotional as [number] var marketTrades = docData.marketNumberOfTrades as [number] var dates = docData.dates as [string] var closings = docData.close as [number] var volume = docData.marketVolume as [number] path = `apiUrl to get data` const options = { method : 'GET', uri : path, resolveWithFullResponse : true, } await req(options).then(async(response)=>{ if(response.statusCode === 200){ const resultData = JSON.parse(response.body) const updatedPrices = resultData as [IntradayPrice] updatedPrices.forEach(function(value){ if(value.close !== undefined){ closings.splice(0,1) marketTrades.splice(0,1) marketNotional.splice(0,1) labels.splice(0,1) dates.splice(0,1) volume.splice(0,1) closings.push(value.close) dates.push(value.date) if(value.label !== undefined){ labels.push(value.label) } else { labels.push("") } if(value.marketNotional !== undefined) { marketNotional.push(value.marketNotional) } else { marketNotional.push(0) } if(value.marketNumberOfTrades !== undefined) { marketTrades.push(value.marketNumberOfTrades) } else { marketTrades.push(0) } if(value.marketVolume !== undefined) { volume.push(value.marketVolume) } else { volume.push(0) } } }) await charts5DRef.doc(ticker).set({lastUpdate : timestamp,close : closings, labels : labels, marketVolume : volume,marketNumberOfTrades : marketTrades, marketNotional : marketNotional, dates : dates}).then(()=>{ console.log(`Updated ${ticker} 5Dchart successfully`) }).catch((error)=>{ console.log(error) }) } }).catch((error)=>{ console.log(error) }) }) }else{ console.log("Markets closed") return }
})
-
Is there a way to use specific values of an object type when creating another object type
The example is for defining a
QuestionMap
andAnswerMap
. There are multiple types of questions that produce different types of answers. So, in order to define the map of answers, you must know the question. Here's an example:const questionMap: QuestionMap = { QUESTION_1: { type: 'boolean' }, QUESTION_2: { type: 'string' } QUESTION_3: { type: 'dropdown', options: { CUSTOM: 'custom answer', UNUSED: 'unused answer' }} } const answerMap: Answer<QuestionMap> = { QUESTION_1: true, QUESTION_2: 'custom answer' }
In this best attempt for the type of
AnswerMap
, it doesn't use theAnswer
type for a specific question.type AnswerMap<TQuestionMap extends QuestionMap> = Partial<Record<QuestionId, Answer<TQuestionMap[QuestionId]>>>
What is the correct definition for
AnswerMap
that properly types each answer based on the question's type?