StencilJS passing in array of objects from React - reactjs

I have a stencil component that takes an array of objects to display. I am trying to pass that array in from my react app, and have been following this example to do that. If i log the component with the array passed in, i can see that it appears to be formed correctly.
The actual component I built to create the reference to handle the array is where my problem is, and I just can't figure out where I made the mistake, as there is an issue with the example. My component inside my react app, which returns the stencil component is as follows
const ActionBar: React.FC<{ pageActions: pageActionsObj }> = ({ pageActions }) => {
const elementRef = useRef(null);
useEffect(() => {
(elementRef.current).pageActionList = pageActions;
}, [pageActions]);
return <pw-actionbar ref={elementRef}></pw-actionbar>;
};
and I try to use it as a regular component as such
<ActionBar pageActions={pageActionsObj}/>
Where the array that im passing as the prop is
export const pageActionsObj = [
{
name: "Users",
page: "partner",
icon: "pw-i-filled-multiple-users",
onpage: false
},
{
name: "Billing",
page: "billing",
icon: "pw-i-filled-billing",
onpage: false
},
{
name: "Helpdesk",
page: "help-desk",
icon: "pw-i-filled-help",
onpage: false
},
{
name: "My Profile",
page: "profile",
icon: "pw-i-filled-user",
onpage: true
},
{
name: "Sign Out",
page: "sign-out",
icon: "pw-i-filled-sign-out",
onpage: false
},
];
I very strongly believe that the way the component is set up, paired with the way I am passing in the array as a prop is causing the issue. I have been stuck for hours. What am i missing?

Related

Next JS - how to give object a unique id?

I am trying to give my object a unique id by using Math.random, but I am getting the error below. If I change the Math.random to a integer like 4, the error goes away but I need the id to be unique.
Unhandled Runtime Error
Error: Text content does not match server-rendered HTML.
See more info here: https://nextjs.org/docs/messages/react-hydration-error
const data = [
{
id: Math.random(),
sentence: 'hello there',
},
{
id: Math.random(),
sentence: 'bye!',
},
]
export default function Index(props) {
return (
<div>
<h1>{data[0].id}</h1>
<h1>{data[0].sentence}</h1>
<h1>{data[1].id}</h1>
<h1>{data[1].sentence}</h1>
</div>
)
}
As explained in my comment and one of the answers, the problem is happening because Next.js pre-renders pages, therefore, the random number generated by Math.random() when pre-rendering the page on the server doesn't match the random number generated client-side when hydration occurs.
I'm not quite sure what you're trying to achieve by setting random ids to what seems to be "dummy data" (You could do it manually with constant values that will match both server and client-side) but I understand that this might be a simplified example.
You have a couple of options, typically you'd want to move any random generation code/logic inside a useEffect hook so it executes on the client-side only.
Another solution would be to move your "dummy data" and the rendering of this data to a separate component, let's call it DummyComponent:
const data = [
{
id: Math.random(),
sentence: 'hello there',
},
{
id: Math.random(),
sentence: 'bye!',
},
]
const DummyComponent = () => (
<>
<h1>{data[0].id}</h1>
<h1>{data[0].sentence</h1>
<h1>{data[1].id}</h1>
<h1>{data[1].sentence}</h1>
</>
)
export default DummyComponent
And import it dynamically on your page disabling ssr:
import dynamic from 'next/dynamic'
const DummyComponent = dynamic(() => import('../components/DummyComponent'), {
ssr: false,
})
export default function Index(props) {
return (
<div>
<DummyComponent />
</div>
)
}
Use the uuid npm package to generate unique id's.
import { v4 } from "uuid";
const data = [
{
id: v4(),
sentence: 'hello there',
},
{
id: v4(),
sentence: 'bye!',
},
]
First, the page is rendered on the server, then returned to the client to rehydrate, two times initializing data would cause different ID values. This is called hydration mismatch, which is solved by useId in React 18
But using NextJS, you could solve this problem by initializing data on server to keep it consistent
export default function Index(props) {
return (
<div>
<h1>{props.data[0].id}</h1>
<h1>{props.data[0].sentence}</h1>
<h1>{props.data[1].id}</h1>
<h1>{props.data[1].sentence}</h1>
</div>
);
}
export async function getServerSideProps(context) {
return {
props: {
data: [
{
id: Math.random(),
sentence: 'hello there',
},
{
id: Math.random(),
sentence: 'bye!z',
},
],
}, // will be passed to the page component as props
};
}
Stackblitz demo
References
getServerSideProps

What is the proper way to update multiple objects in an array of objects in react?

Making a watchlist app for stocks/crypto to learn react.
I have watchlist state in this format (using useState hook):
[
{ id: "1btc", name: "bitcoin", price: "7500" },
{ id: "1eth", name: "ethereum", price: "500" },
{ id: "2xmr", name: "monero", price: "200" },
{ id: "1ltc", name: "litecoin", price: "10" },
];
every 3 seconds server sends a batch of available price updates over websocket connection.
sometimes only a handful of coins have new info so update message might look like so:
[
{ id: "2xmr", price: "225" },
{ id: "1btc", price: "8600" },
];
Is it possible to update the watchlist so that only the updated coins in the list rerender as opposed to rerendering the entire list every time an update message is received? What is the best way to handle this situation?
You could let each object have its own component and then pass the object properties in the components and then use React.memo() so that it would only cause a render if it detects a change.
By default it will only shallowly compare the objects in the props which should be enough for you. But if its more complex, then you can also write a custom function.
As per docs
function MyComponent(props) {
/* render using props */
}
function areEqual(prevProps, nextProps) {
/*
return true if passing nextProps to render would return
the same result as passing prevProps to render,
otherwise return false
*/
}
export default React.memo(MyComponent, areEqual);

How is destructuring happening in this particular example?

I am new to react and I am trying to build a folder tree component in react.
I am following this tutorial - link
you can view the code here - sandbox
I am confused about how destructing is happening in this case
const Tree = ({ data, children }) => {
const isImparative = data && !children;
return (
<StyledTree>
{isImparative ? <TreeRecursive data={data} /> : children}
</StyledTree>
);
};
Tree.File = File;
Tree.Folder = Folder;
const structure = [
{
type: "folder",
name: "src",
childrens: [
{
type: "folder",
name: "Components",
childrens: [
{ type: "file", name: "Modal.js" },
{ type: "file", name: "Modal.css" }
]
},
{ type: "file", name: "index.js" },
{ type: "file", name: "index.html" }
]
},
{ type: "file", name: "package.json" }
];
export default function App() {
return (
<div className="App">
<Tree data={structure} />
</div>
);
I am confused in this particular line
const Tree = ({ data, children }) => {
How is the destructing happening her?
You are destructing { data, children } from structure array.
How does it automatically decide what to pick inside structure for data and what to pick for children?
There are no field named data and children as well. So how is this working here?
When you define a component (here its Tree) it might expect props. Inside the props there is all the different properties which we are passing for example for the below the data is a prop.
<Tree data={structure} />
So, The destructuring is done for the prop. if you remove the object for the const Tree = ({ data, children }) and write prop instead of it and check you will see the different props passed to the component.
If it was like below, there would be another component called children which would contain the <div>My child</div>. This is useful for making reusable components.
<Tree data={structure}><div>My child</div><Tree/>

React - setting state with imported data keeps getting updated

I am importing an array of data like
import { menuData } from '../../data/menu-data';
data being:
export const menuData = [
{ id: 'all', title: 'Select all', icon: '', selected: false },
{ id: 'overview', title: 'Overview', icon: 'user-check', selected: false },
{
id: 'about',
title: 'About',
icon: 'info-circle',
selected: false,
partSelected: false,
}
];
I then initialise some state in my parent component like:
const [downloadMenu, setMenuState] = useState([...menuData]);
I pass "downloadMenu" state to my child compionent and absorb it as a prop and then create a copy of it which gets mutated i.e.
const updatedMenu = [...downloadMenu];
i then call this function which is in the parent and is passed down as a prop to the child component
onMenuChange(updatedMenu);
const handleMenuChange = (menuState) => {
setMenuState(menuState)
}
This works but when i try and reset the state to the initial menuData it doesnt work. MenuData is getting mutated aswell. Why is this?
I am calling this again - setMenuState([...menuData]);
but menuData is the same as the downloadMenu state.
Using ... only creates a shallow copy - a new list is created, but the items inside it remain the same. The objects in the list aren't copied, but remain as a reference to the original objects in menuData.
The solution is either to create a new object when you change menuData or create a deep copy.
A deep copy can be created using JSON - JSON.parse(JSON.stringify(menuData)) or a library like lodash - _.cloneDeep(menuData).

react how to prevent default on setState without event

Let's say you have a defined function containing a this.setState in a react component which is not fired by an event.
How can you do preventDefault() in order to keep the current scroll on the page?
I create a sandbox to illustrate this behaviour:
https://codesandbox.io/embed/damp-night-r92m5?fontsize=14&hidenavigation=1&theme=dark
When groups are defined and something fire the renderer, the page scroll on top. This does not happend id groups are not defined or contain an empty array...
How can I prevent this scrolling
First of all, I think your issue was nothing to do with event but your component is triggering render.
Based on your sandbox that you provided, you are actually declaring a new array each time the component renders. Meaning that React will assume that your [{ id: 1, content: "group1" }, { id: 2, content: "group2" }] is a new instance, even though all the items in the array is the same.
This line is causing your issue :
groups={[{ id: 1, content: "group1" }, { id: 2, content: "group2" }]}
Method 1: Move your groups variables into state
const [groups, setGroups] = useState([{ id: 1, content: "group1" }, { id: 2, content: "group2" }]);
This way React will not rerender your App until you call setState ( In this case, setGroups )
Method 2: Move your groups outside of your App function
const groups = [{ id: 1, content: "group1" }, { id: 2, content: "group2" }];
function App() {
... App Codes
}
In this way, React will not rerender your App since groups is not declaring within App.
Method 3: Using Memoization useMemo React Hook
const groups = useMemo(() => [{ id: 1, content: "group1" }, { id: 2, content: "group2" }], []);
The second argument in your useMemo function defines your dependencies array, setting it to empty array means that the value will never change. Hence React will not rerender your App.
In your render:
groups={groups}

Resources