Derive a local state in React - reactjs

I would like to derive a local state in my React app and use it in the same component.
import * as React from 'react'
import * as ReactDOM from 'reactDOM'
function App(){
const [origins, setorigins] = React.useState([1, 45, 7, 11]) // source of truth
const [muted, setmuted] = React.useState([]) // derive from source
React.useEffect(() => {
evolve()
}, [origins])
// Transforms [23] => [{origin: 23, muted: 24}]
const evolve = () => {
setmuted(origins.map(o => ({origin: o, muted: o += 1})))
}
const addEntity = () => {
// add random integer between 1 and 45
const newEntity = Math.floor(Math.random() * 45 ) + 1
setorigins([...origins, newEntity])
}
return (
<>
<button onClick={addEntity}>Add entity random</button>
{muted.map(m => (
<div className="entity">
<p>Origin : {m.origin}</p>
<p>Muted: {m.muted}</p>
</div>
)) }
</>
)
}
ReactDOM.render(
<App />,
document.getElementById('reactz')
)
Can you improve this code above ?
Any performance issues I should be warned of ?

Definitely I was overthinking. The simpliest way IMO is to copy and transform the source of truth on the fly as described by Kent on his blog.
import * as React from 'react'
import * as ReactDOM from 'reactDOM'
function App(){
const [origins, setorigins] = React.useState([1, 45, 7, 11]) // source of truth
const muted = evolve(origins) // derived state (useMemo ?)
// Transforms [23] => [{origin: 23, muted: 24}]
function evolve(data) {
return data.map(o => ({origin: o, muted: o += 1}))
}
const addEntity = () => {
// add random integer between 1 and 45
const newEntity = Math.floor(Math.random() * 45 ) + 1
setorigins([...origins, newEntity])
}
return (
<>
<button onClick={addEntity}>Add entity random</button>
{muted.map(m => (
<div className="entity">
<p>Origin : {m.origin}</p>
<p>Muted: {m.muted}</p>
</div>
)) }
</>
)
}
ReactDOM.render(
<App />,
document.getElementById('reactz')
)
And for computational optimization, make use of useMemo hook.

Related

Update an array element in React Hookstate

I have an application using React Hookstate.
For one state I am using an Array. I want to update a single element in this array.
But the updated element always is wrapped inside a proxy tag.
import React from 'react';
import { useState, none, State } from '#hookstate/core';
const Test: React.FC = () => {
const books: State<Array<any>> = useState([
'Harry Potter',
'Sherlock Holmes',
'Swiss Family Robinson',
'Tarzon',
]);
const SwapItems = () => {
// books.merge((p) => ({ 1: p[0], 0: p[1] })); // this way it works.
const reOrderedItem = books.nested(0);
books.nested(0).set(none);
books.set((p) => {
p.splice(0, 0, reOrderedItem);
return p;
});
};
return (
<div>
<ol>
{books.map((book) => {
return <li>{book.get()}</li>;
})}
</ol>
<br />
<button onClick={SwapItems}>Swap Books</button>
</div>
);
};
export default Test;
//error
react-dom.development.js:11393 Uncaught RangeError: Maximum call stack size exceeded.
I'm pretty sure you just need to add .value as follows:
const reOrderedItem = books.nested(0).value;
I'm not sure why you don't prefer the update version that works though -- that seems much simpler.
Alternatively, if you want to use set, why not work with the items directly?
const reOrderedItem = books.nested(0).value;
books.nested(0).set(books.nested(1).value);
books.nested(1).set(reOrderedItem);
Alternatively, you could do the manipulation inside set like so (but you're right that this is inefficient for large arrays):
const SwapItems = () => {
books.set((p) => {
const [a, b, ...rest] = p;
return [b, a, ...rest];
});
};
Actually, I wanted to reorder a certain item on the list.
import React from 'react';
import { useState, State } from '#hookstate/core';
const Test: React.FC = () => {
const books: State<Array<any>> = useState([
'Harry Potter',
'Sherlock Holmes',
'Swiss Family Robinson',
'Tarzon',
]);
const ReorderItems = () => {
books.set((p) => {
const reOrderedItem = p[2];
p.splice(2, 1);
p.splice(0, 0, reOrderedItem);
return p;
});
};
return (
<div>
<ol>
{books.map((book) => {
return <li>{book.get()}</li>;
})}
</ol>
<br />
<button onClick={ReorderItems}>Swap Books</button>
</div>
);
};
export default Test;

React Three fiber shows the display in blank from i-pad

I am building my Portfolio using React three fiber, it shows the display on mobile and laptop or PC.
But when I'm trying to see on i-pad the display is blank.
I am trying to solve this problem, but I could not find any solution.
I will be happy if can give me some advice.
This is my code
import React, { useState, useRef, useEffect } from 'react';
import * as THREE from 'three';
import { extend, Canvas, useFrame } from 'react-three-fiber';
import * as meshline from 'threejs-meshline';
import Typical from 'react-typical';
// firebase
import firebase from '../firebase/firebase.utils';
// custom hook
import useAuthor from '../hooks/getAuthor';
// component
import Spinner from './Spinner';
import '../assets/styles/components/ThreeFiber.scss';
extend(meshline);
const numLines = 100;
const lines = new Array(numLines).fill();
const colors = ['#f6f6f6', '#fff8cd', '#6495ED'];
function Fatline() {
const material = useRef();
const [color] = useState(
() => colors[parseInt(colors.length * Math.random())]
);
const [ratio] = useState(() => 0.6 + 0.7 * Math.random());
const [width] = useState(() => Math.max(0.1, 0.1 * Math.random()));
// Calculate wiggly curve
const [curve] = useState(() => {
const pos = new THREE.Vector3(
30 - 60 * Math.random(),
-5,
10 - 20 * Math.random()
);
const points = new Array(30)
.fill()
.map(() =>
pos
.add(
new THREE.Vector3(
2 - Math.random() * 4,
4 - Math.random() * 2,
5 - Math.random() * 10
)
)
.clone()
);
return new THREE.CatmullRomCurve3(points).getPoints(500);
});
// Hook into the render loop and decrease the materials dash-offset
useFrame(() => {
material.current.uniforms.dashOffset.value -= 0.0005;
});
return (
<mesh>
<meshLine attach="geometry" vertices={curve} />
<meshLineMaterial
attach="material"
ref={material}
transparent
depthTest={false}
lineWidth={width}
color={color}
dashArray={0.2}
dashRatio={ratio}
/>
</mesh>
);
}
function Scene() {
let group = useRef();
let theta = 0;
// Hook into the render loop and rotate the scene a bit
useFrame(() => {
group.current.rotation.set(
0,
8 * Math.sin(THREE.Math.degToRad((theta += 0.02))),
0
);
});
return (
<group ref={group}>
{lines.map((_, index) => (
<Fatline key={index} />
))}
</group>
);
}
const ThreeFiber = React.memo(() => {
const collectionRef = firebase.firestore().collection('author');
const authorCollection = useAuthor(collectionRef);
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
}, []);
return (
<div className="Hero_container">
<Canvas
camera={{ position: [0, 60, 10], fov: 150 }}
resize={{ scroll: false }}
>
<Scene />
</Canvas>
{loading ? (
authorCollection.map((author) => (
<h4 key={author.informations} className="Hero_content">
Hi! <br /> I'm {author.informations.name}, <br />
<Typical
loop={Infinity}
wrapper="b"
steps={[
'Front-end Developer with React',
1500,
'a self-taught Web Engineer',
1500,
]}
/>
</h4>
))
) : (
<Spinner />
)}
</div>
);
});
Thank you for your help.....

Conditional rendering and inline IF doesn't work

I have two problems:
the conditional rendering is not working well on Statistics component
inline IF on the line 46 didn't work so I commented out. I checked the values with typeof jc func and all values are numbers
Please let me know if you spot what I've done wrong.
Thanks a lot!
import React, {useState} from 'react'
import ReactDOM from 'react-dom'
const Feedback = (props) => (
<button onClick={props.handleFeedback}>{props.text}</button>
)
const Statistics = (props) => {
console.log(props);
if (props.total.length === 0) {
return (
<div>empty</div>
)
}
return (
<div>
<p>all {props.total}</p>
<p>average {props.average}</p>
<p>positive {props.positive}</p>
</div>
)
}
const App = () => {
const [good, setGood] = useState(0)
const [neutral, setNeutral] = useState(0)
const [bad, setBad] = useState(0)
const setToGood = (newValue) => () => {
setGood(newValue)
}
const setToNeutral = (newValue) => () => {
setNeutral(newValue)
}
const setToBad = (newValue) => () => {
setBad(newValue)
}
const total = good + neutral + bad;
const average = (good - bad) / total
const positive = (good / total * 100 === 0 ) ? '' : (good / total * 100)
console.log(positive);
return (
<div>
<h1>give feedback</h1>
<Feedback handleFeedback={setToGood(good +1)} text="good" />
<Feedback handleFeedback={setToNeutral(neutral +1)} text="neutral" />
<Feedback handleFeedback={setToBad(bad +1)} text="bad" />
<h1>statisctics</h1>
<p>good {good}</p>
<p>neutral {neutral}</p>
<p>bad {bad}</p>
<Statistics total={total} average={average} positive={positive}/>
</div>
)
}
ReactDOM.render(
<App />,
document.getElementById('root')
)
Inside IF don't use props.total.length as total is of number type not string.

react shows error when submit button click

I am new to React. I don't understand why it's showing an error. I need to repeat the array object but when It reaches the last array it does not restart.https://codesandbox.io/s/generate-quote-xpu1q
index.js:
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
rootElement
);
app.js:
import React, { useState, useEffect } from "react";
import "./styles.css";
// import { qutoes } from "./Fetch";
export default function App() {
// const data = qutoes;
const [data, setData] = useState("loading");
const [index, setIndex] = useState(0);
const qutoesBtn = () => {
if (index === data.length - 1) {
setIndex(0);
} else {
setIndex(index + 1);
}
};
useEffect(() => {
fetch("https://type.fit/api/quotes")
.then(res => res.json())
.then(res2 => {
console.log(res2.slice(0, 10));
const lists = res2.slice(0, 10);
setData(lists);
});
}, []);
return (
<div className="App">
<h1>Generate Quote</h1>
<h4>
{data === "loading" ? (
"loading...!!"
) : (
<div>
My qutoes is ---- <br />
<span> {data[index].text}</span> <br /> Author is --
{data[index].author}
</div>
)}
</h4>
<button onClick={qutoesBtn}>Generate Quote</button>
</div>
);
}
You should change your condition to update once the index reaches data.length - 1
if (index === (data.length - 1)) {
setIndex(0);
}
Remember that given an array [1, 2, 3] the length is 3, but the maximum index is 2 because arrays indexes start at 0, so when index is equal to the data.length React is going to try to access that position giving you the error that you're experiencing.
It is because you are not checking if the array has reached the last item in the button click, So if you don't check it in the button click then it will increment the index again before it even gets to check , So change your code to this:
const qutoesBtn = () => {
if (index === data.length - 1) {
setIndex(0);
} else {
setIndex(index + 1);
}
};
Here -1 refers to the last item in the array
I think you need a condition where if the array reaches the end
Then start it back at zero
You wanna Check if the next item exceeds the length and you want to check it inside the qutoesBtn
const qutoesBtn = () => {
setIndex(index + 1);
if (index + 1 >= data.length) {
setIndex(0);
}
console.log(index);
};
CodeSandbox here

React Error: Hooks can only be called inside the body of a function component

I know this is very common error Invariant Violation: Hooks can only be called inside the body of a function component but still I am not able to figure it out how to fix it in my code.
What I understood is the issue is with useMemo hook in my code ? please guide me. As per my understanding, problem is mismatching versions of React and ReactDOM or it is related to I am not calling my hooks properly. Parent component is using react 15 and child component's code refer react 16.8 code.
Problem is with specific to this code -
/**
* Get the difference from parseDiff in a formatLines
*/
const diff = useMemo(() => {
const diffText = formatLines(
diffLines(props.startupConfigData, props.runningConfigData),
{
context: 3
}
);
const [diff] = parseDiff(diffText, { nearbySequences: "zip" });
return diff;
}, [props.startupConfigData, props.runningConfigData]);
const { type, hunks } = diff;
/**
* Token used for useMemo
*/
const tokens = useMemo(() => tokenize(hunks), [hunks]);
My full component code -
import React, { useMemo } from "react";
import { diffLines, formatLines } from "./unidiff";
import { parseDiff, Diff, Hunk } from "react-diff-view";
import "./diff.less";
import tokenize from "./tokenize";
import { DnxLoader } from "*external*/#cisco-dna/dnx-react-components";
import i18n from "amdi18n-loader!~/nls/i18_compliance";
import { labels } from "../constants";
import { getStatusComplianceType } from "../common";
const DiffViewer = props => {
const { compareWith } = props;
/**
* Get the difference from parseDiff in a formatLines
*/
const diff = useMemo(() => {
const diffText = formatLines(
diffLines(props.startupConfigData, props.runningConfigData),
{
context: 3
}
);
const [diff] = parseDiff(diffText, { nearbySequences: "zip" });
return diff;
}, [props.startupConfigData, props.runningConfigData]);
const { type, hunks } = diff;
/**
* Token used for useMemo
*/
const tokens = useMemo(() => tokenize(hunks), [hunks]);
/**
* Get text based on selection - startup/running
*/
const getStartupText = () => {
if (compareWith == undefined || compareWith === "startup") {
return i18n.label_startup_configuration;
} else {
return i18n.label_running_configuration;
}
};
/**
* Get date format
* #param {*} date
*/
const getFormattedDate = date => {
var lastUpdateTimeData;
const notAvailable = i18n.label_non_applicable;
if (date !== null) {
lastUpdateTimeData = moment(date).format(labels.lastUpdateTimeFormatAmPm);
} else {
lastUpdateTimeData = notAvailable;
}
return lastUpdateTimeData;
};
const statusDisplayDetails = getStatusComplianceType(
"COMPLIANT",
true
);
/**
* return function
*/
return (
<div className="margin-left-right">
{hunks ? (
<div>
<div className="flex">
<div className="startup-text">
{getStartupText()}
<div className="font-size-10">
{i18n.label_no_of_lines}:{" "}
{props.startUpFile.totalNoOfLines}
</div>
<div className="font-size-10">{i18n.label_archived_on}: {getFormattedDate(props.startUpFile.createdTime )}</div>
</div>
<div className="running-text">
{i18n.label_running_configuration}
<div className="font-size-10">
{i18n.label_no_of_lines}:{" "}
{props.runningFile.totalNoOfLines}
</div>
<div className="font-size-10">{i18n.label_archived_on}: {getFormattedDate(props.runningFile.createdTime)}</div>
</div>
</div>
{ Array.isArray(hunks) && hunks.length ? (
<div className="diff-div">
<Diff
viewType="split"
diffType={type}
hunks={hunks}
tokens={tokens}
>
{hunks =>
hunks.map(hunk => <Hunk key={hunk.content} hunk={hunk} />)
}
</Diff>
</div>
) : (
<div className="div-margin-diff">
{statusDisplayDetails}
<span>{compareWith === "running" ? i18n.label_running_config_compliant : i18n.label_running_startup_config_compliant}</span>
</div>
)
}
</div>
) : (
<div className="loading-icon">
<DnxLoader color="#026E97" size="54" label="Loading..." />
</div>
)}
</div>
);
};
export default DiffViewer;
Can someone please point what is the problem ?

Resources