Side panel with components loading dynamically based on context - reactjs

With react: how can I render a side panel component with different components depending on the type of action. To give a concrete example:
On the main page, I have a table with some rows. Every row may be of a different "type" (Row 1 may be of type A, Row 2 may be of type B.
When I click on a row that's type A, I would like to render components related to the type A row, and different components for type B row.
You can almost define the panel like a "widget" area, where I can load different components based on different context, or action I perform outside of that area.
Below is what I am trying to do.

This can be done by changing the data stored on state of the selected row type.
For example:
state = {
currently_selected_type : ""
}
In your render function, we can have something like this:
render(){
let SidePanelComponent = null;
if(this.state.currently_selected_type === "A"){
SidePanelComponent = (<div>A</div>);
}
else if(this.state.currently_selected_type === "B"){
SidePanelComponent = (<div>B</div>);
}
return (
<div>
<Table
onClick={
// this type will be passed from the inside Table when rows are clicked based on the row type
(type) => this.setState({currently_selected_type : type})
}
/>
{SidePanelComponent}
</div>
);
}
Inside the Table component you will be rendering the different types of rows, so based on what row is click just send the type to the passed onClick function.
The Table render function can be as follows, for example:
render(){
return (
...
<tr onClick={this.props.onClick("A")}>Row Type A</tr>
<tr onClick={this.props.onClick("B")}>Row Type B</tr>
...
);
}

Ok, I figured it out:
I created a context and a provider
import React, { useState } from 'react';
import PanelContext from './panelContext'
const PanelProvider = ({ children }) => {
const [content, setContent] = useState(undefined);
return (
<PanelContext.Provider
value={{
content: content,
setContent: setContent
}}>
{children}
</PanelContext.Provider>
);
};
export default PanelProvider;
Then I created a usePanel hook:
import PanelContext from "../contexts/panel/panelContext";
const usePanel = () => {
const { content, setContent } = useContext(PanelContext)
return {content, setContent};
}
export default usePanel;
Then in my Table Row component, I use it:
const { setContent } = usePanel();
...
...
const testCallback = (e) => console.log(e.target.value)
...
...
setContent(<Input value="wow!!!!" onChange={testCallback} />)
And finally, on the actual panel component itself:
import { usePanel } from "../../../hooks"
export default ({ }) => {
const { content } = usePanel();
return (
<aside className="contextualizer" id="contextualizer">
<PerfectScrollbar>
<div className="customizer-body">
{content}
</div>
</PerfectScrollbar>
</aside >
);
}
Works perfectly.

Related

How do I pass data when the x button is clicked?

So I have a filter chip, and this filter chip is just passed a text body, and close function like so:
import CloseIcon from '#mui/icons-material/Close';
import "./FilterChip.css";
function FilterChip({textBody, onCloseClick}) {
return <div className="filter-chip">
Category: {textBody} <CloseIcon onClick={onCloseClick} className="filter-chip-close-button"/>
</div>
}
export default FilterChip;
I can render multiple filter chips in one page. How can I tell my parent component that the particular chip's x button has been clicked? Is it possible to pass this data on the onCloseClick function? I need to remove the chip once it's x button has been clicked, and I also need to uncheck it from my list of check boxes in my parent component. This is how I render the chips.
function renderFilterChips() {
const checkedBoxes = getCheckedBoxes();
return checkedBoxes.map((checkedBox) =>
<FilterChip key={checkedBox} textBody={checkedBox} onCloseClick={onChipCloseClick} />
);
}
You should pass an "identifier" for each chip and then use that identifier to find out "what" was clicked by the user. And then you can filter out the clicked chip.
function FilterChip({ textBody, onCloseClick, id }) {
const handleOnClose = (event) => {
onCloseClick(event, id);
};
return (
<div className="filter-chip">
Category: {textBody}{" "}
<CloseIcon onClick={handleOnClose} className="filter-chip-close-button" />
</div>
);
}
Now your onCloseClick should accept a new param id and handle the logic to remove the chip .
Hope it helps.
Sounds like you need checkedBoxes to be in state.
import { useState } from "react"
const initialBoxes = getCheckedBoxes()
function renderFilteredChips() {
const [ checkedBoxes, setCheckedBoxes ] = useState(initialBoxes)
}
Then implement a function to remove a checked box by its index (or if you have a unique key identifier that would be even better)
const onChipCloseClick = (indexToRemove) => {
setCheckedBoxes(state => state.filter((_, chipIndex) => chipIndex !== indexToRemove))
}
Then when you map over the chips, make sure the function that closes the chip has its index, effectively allowing each chip in state to filter itself out of state, which will re-render your chips for you.
import { useState } from "react"
const initialBoxes = getCheckedBoxes()
function renderFilteredChips() {
const [ checkedBoxes, setCheckedBoxes ] = useState(initialBoxes)
const onChipCloseClick = (indexToRemove) => {
setCheckedBoxes(state => state.filter((_, chipIndex) => chipIndex !== indexToRemove))
}
return <>
{checkedBoxes.map((checkedBox, index) => (
<FilterChip
key={index}
textBody={checkedBox}
onCloseClick={() => onChipCloseClose(index)}
/>
})
</>
}
Obligatory note that I haven't checked this and wrote it in Markdown, so look out for syntax errors (:

Like Button with Local Storage in ReactJS

I developed a Simple React Application that read an external API and now I'm trying to develop a Like Button from each item. I read a lot about localStorage and persistence, but I don't know where I'm doing wrong. Could someone help me?
1-First, the component where I put item as props. This item bring me the name of each character
<LikeButtonTest items={item.name} />
2-Then, inside component:
import React, { useState, useEffect } from 'react';
import './style.css';
const LikeButtonTest = ({items}) => {
const [isLike, setIsLike] = useState(
JSON.parse(localStorage.getItem('data', items))
);
useEffect(() => {
localStorage.setItem('data', JSON.stringify(items));
}, [isLike]);
const toggleLike = () => {
setIsLike(!isLike);
}
return(
<div>
<button
onClick={toggleLike}
className={"bt-like like-button " + (isLike ? "liked" : "")
}>
</button>
</div>
);
};
export default LikeButtonTest;
My thoughts are:
First, I receive 'items' as props
Then, I create a localStorage called 'data' and set in a variable 'isLike'
So, I make a button where I add a class that checks if is liked or not and I created a toggle that changes the state
The problem is: I need to store the names in an array after click. For now, my app is generating this:
App item view
localStorage with name of character
You're approach is almost there. The ideal case here is to define your like function in the parent component of the like button and pass the function to the button. See the example below.
const ITEMS = ['item1', 'item2']
const WrapperComponent = () => {
const likes = JSON.parse(localStorage.getItem('likes'))
const handleLike = item => {
// you have the item name here, do whatever you want with it.
const existingLikes = likes
localStorage.setItem('likes', JSON.stringify(existingLikes.push(item)))
}
return (<>
{ITEMS.map(item => <ItemComponent item={item} onLike={handleLike} liked={likes.includes(item)} />)}
</>)
}
const ItemComponent = ({ item, onLike, liked }) => {
return (
<button
onClick={() => onLike(item)}
className={liked ? 'liked' : 'not-liked'}
}>
{item}
</button>
)
}
Hope that helps!
note: not tested, but pretty standard stuff

Observe (get sized) control (listen to events) over a nested component in the react and typescript application via the forwardRef function

I have a functional component called MyDivBlock
const MyDivBlock: FC<BoxProps> = ({ }) => {
{getting data...}
return (
<>
<div className='divBlock'>
{data.map((todo: { id: string; title: string }) =>
<div key={todo.id}>{todo.id} {todo.title} </div>)}
</div>
</>
);
};
I use it in such a way that MyDivBlock is nested as a child of
const App: NextPage = () => {
return (
<div>
<Box >
<MyDivBlock key="key0" areaText="DIV1" another="another"/>
</Box>
</div>
)
}
Note that MyDivBlock is nested in Box and MyDivBlock has no ref attribute. This is important because I need to write Box code with no additional requirements for my nested children. And anyone who will use my Box should not think about constraints and ref attributes.
Then I need to get the dimensions of MyDivBlock in the code of Box component, and later attach some event listeners to it, such as scrolling. These dimensions and listeners will be used in the Box component. I wanted to use Ref to control it. That is, the Box will later observe changes in the dimensions and events of MyDivBlock by creating a ref-reference to them
I know that this kind of parent-child relationship architecture is implemented through forwardRef
And here is the Box code:
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
export interface BoxProps extends React.ComponentProps<any> {
children?: Element[];
className: string;
}
export const Box: React.FC<BoxProps> = ({ children, ...rest }: BoxProps): JSX.Element => {
const childRef = useRef<HTMLDivElement>();
const ChildWithForwardRef = forwardRef<HTMLDivElement>((props, _ref) => {
const methods = {
show() {
if (childRef.current) {
console.log("childRef.current is present...");
React.Children.forEach(children, function (item) {
console.log(item)})
console.log("offsetWidth = " + childRef.current.offsetWidth);
} else {
console.log("childRef.current is UNDEFINED");
}
},
};
useImperativeHandle(_ref, () => (methods));
return <div ref={childRef}> {children} </div>
});
ChildWithForwardRef.displayName = 'ChildWithForwardRef';
return (
<div
className={'BoxArea'}>
<button name="ChildComp" onClick={() => childRef.current.show()}>get Width</button>
<ChildWithForwardRef ref={childRef} />
</div>
);
}
export default Box;
The result of pressing the button:
childRef.current is present...
[...]
$$typeof: Symbol(react.element) key: "key0" props: {areaText: 'DIV1', another: 'another'}
[...] Object
offsetWidth = undefined
As you can see from the output, the component is visible through the created ref. I can even make several nested ones and get the same for all of them.
But the problem is that I don't have access to the offsetWidth and other properties.
The other challenge is how can I add the addEventListener?
Because it works in pure Javascript with their objects like Element, Document, Window or any other object that supports events, and I have ReactChildren objects.
Plus I'm using NextJS and TypeScript.
Didn't dive too deep into the problem, but this may be because you are passing the same childRef to both div inside ChildWithForwardRef and to ChildWithForwardRef itself. The latter overwrites the former, so you have the method .show from useImperativeHandle available but not offsetWidth. A quick fix is to rewrite ChildWithForwardRef to use its own ref:
const ChildWithForwardRef = forwardRef<HTMLDivElement>((props, _ref) => {
const ref = useRef<HTMLDivElement>()
const methods = {
show() {
if (ref.current) {
console.log("ref.current is present...");
React.Children.forEach(children, (item) => console.log(item))
console.log("offsetWidth = " + ref.current.offsetWidth);
} else {
console.log("ref.current is UNDEFINED");
}
},
};
useImperativeHandle(_ref, () => (methods));
// Here ref instead of childRef
return <div ref={ref}> {children} </div>
});
But really I don't quite get why you would need ChildWithForwardRef at all. The code is basically equivalent to this simpler version:
const Box: React.FC<BoxProps> = ({ children, ...rest }: BoxProps): JSX.Element => {
const childRef = useRef<HTMLDivElement>();
const showWidth = () => {
if(childRef.current) {
console.log("childRef.current is present...");
React.Children.forEach(children, item => console.log(item))
console.log("offsetWidth = " + childRef.current.offsetWidth);
} else {
console.log("childRef.current is UNDEFINED");
}
}
return (
<div className={'BoxArea'}>
<button name="ChildComp" onClick={showWidth}>get Width</button>
<div ref={childRef}>{children}</div>
</div>
);
}
You can't solve this completely with React. I solved it by wrapping the child component, making it take the form of the parent.

How should I update individual items' className onClick in a list in a React functional component?

I'm new to React and I'm stuck trying to get this onClick function to work properly.
I have a component "Row" that contains a dynamic list of divs that it gets from a function and returns them:
export function Row({parentState, setParentState}) {
let divList = getDivList(parentState, setParentState);
return (
<div>
{divList}
</div>
)
}
Say parentState could just be:
[["Name", "info"],
["Name2", "info2"]]
The function returns a list of divs, each with their own className determined based on data in the parentState. Each one needs to be able to update its own info in parentState with an onClick function, which must in turn update the className so that the appearance of the div can change. My code so far seems to update the parentState properly (React Devtools shows the changes, at least when I navigate away from the component and then navigate back, for some reason), but won't update the className until a later event. Right now it looks like this:
export function getDivList(parentState, setParentState) {
//parentState is an array of two-element arrays
const divList = parentState.map((ele, i) => {
let divClass = "class" + ele[1];
return (
<div
key={ele, i}
className={divClass}
onClick={() => {
let newParentState =
JSON.parse(JSON.stringify(parentState);
newParentState[i][1] = "newInfo";
setParentState(newParentState);}}>
{ele[0]}
</div>
)
}
return divList;
}
I have tried to use useEffect, probably wrong, but no luck. How should I do this?
Since your Row component has parentState as a prop, I assume it is a direct child of this parent component that contains parentState. You are trying to access getDivList in Row component without passing it as a prop, it won't work if you write your code this way.
You could use the children prop provided by React that allow you to write a component with an opening and closing tag: <Component>...</Component>. Everything inside will be in the children. For your code it would looks like this :
import React from 'react';
import { render } from 'react-dom';
import './style.css';
const App = () => {
const [parentState, setParentState] = React.useState([
['I am a div', 'bg-red'],
['I am another div', 'bg-red'],
]);
React.useEffect(
() => console.log('render on ParentState changes'),
[parentState]
);
const getDivList = () => {
return parentState.map((ele, i) => {
return (
<div
key={(ele, i)}
className={ele[1]}
onClick={() => {
// Copy of your state with the spread operator (...)
let newParentState = [...parentState];
// We don't know the new value here, I just invented it for the example
newParentState[i][1] = [newParentState[i][1], 'bg-blue'];
setParentState(newParentState);
}}
>
{ele[0]}
</div>
);
});
};
return <Row>{getDivList()}</Row>;
};
const Row = ({ children }) => {
return <>{children}</>;
};
render(<App />, document.getElementById('root'));
And a bit of css for the example :
.bg-red {
background-color: darkred;
color: white;
}
.bg-blue {
background-color:aliceblue;
}
Here is a repro on StackBlitz so you can play with it.
I assumed the shape of the parentState, yu will have to adapt by your needs but it should be something like that.
Now, if your data needs to be shared across multiple components, I highly recommand using a context. Here is my answer to another post where you'll find a simple example on how to implement a context Api.

passing a state hook to another element using props is not working in React?

I have tow components one should have the states of showing and hiding an icon. the other should at least show that icon on click since the default state in not showing anything, so I am trying to pass down the showIcon function down to the other element using props but its not working showing an error showIcon is not a function
//Component A Row
import anItem from './anItem';
function Row(props) {
const [iconState, setIconState] = useState([]);
const icon = <FontAwesomeIcon icon={["fas", "check"]}/>
// from anItem component
const showIcon = ()=>{
setIconState([icon])
}
// from this component
const removeIcon = ()=>{
setIconState([])
}
// Pass the funtion down to the below componant so we can hide the element from there
let item = [<anItem icon=iconState showIcon={showIcon}/>]
return (
<li className="day-row check faderin" onClick={()=> reomveIcon()}>
// render all the items in the initial state
{item}
</li>
)
}
// Component B anItem
function anItem(props) {
return (
<div onClick={() =>{props.showIcon}>{porps.icon}</div>
)
}
export default anlItem
Here I see some typo:
let item = [<anItem icon={iconState} showIcon={showIcon}/>] // add curly braces
Make sure you correctly define the functions below (with const keyword):
const showIcon = ()=> {
setIconState([icon])
}
const removeIcon = ()=> {
setIconState([])
}
I see also another problem in your anItem component:
function anItem(props) {
return (
<div onClick={() => props.showIcon()}>{porps.icon}</div> // here onClick param
)
}
Or like this:
function anItem(props) {
return (
<div onClick={props.showIcon}>{porps.icon}</div> // here onClick param
)
}

Resources