I have few components import into this file. It will render it depending if match the roles.
As example, we have "A" role in this user. It should render AdminDashboard and CustomerDashboard. How to do in this way if I save details in DashboardComponents Object and try to render component if filter matched my condition.
I will pass into the props into the component too.
import AdminDashboard from "../AdminDashboard";
import ProjectDashboard from "../ProjectDashboard";
import CustomerDashboard from "../CustomerDashboard"
const DashboardComponents = [
{ id: 1, component: AdminDashboard, roles: [ "A" ] },
{ id: 2, component: ProjectDashboard, roles: [ "C", "D" ] },
{ id: 3, component: CustomerDashboard, roles: [ "A", "B" ] },
]
const DashboardComponent = () => {
/**
I tried use React.cloneElement for component Dashboard which from DashboardComponents.
it can't be render.
**/
return (
/** render the match DashboardComponents with props data={ data } in here **/
)
}
I knew we can do it in this way. but it look messy for me if I have 10 Dashboard.
import AdminDashboard from "../AdminDashboard";
import ProjectDashboard from "../ProjectDashboard";
import CustomerDashboard from "../CustomerDashboard"
const DashboardComponent = () => {
return (
<>
{
condition === "A" && (
<AdminDashboard data={ data } />
)
}
{
condition === "A" && (
<ProjectDashboard data={ data } />
)
}
{
condition === "A" && (
<CustomerDashboard data={ data } />
)
}
</>
)
}
You can try:
const AdminDashboard = ({data}) => {
return (
<div>
AdminDashboard: {data}
</div>
)
}
const ProjectDashboard = ({data}) => {
return (
<div>
ProjectDashboard: {data}
</div>
)
}
const CustomerDashboard = ({data}) => {
return (
<div>
CustomerDashboard: {data}
</div>
)
}
const DashboardComponents = [
{ id: 1, component: AdminDashboard, roles: [ "A" ] },
{ id: 2, component: ProjectDashboard, roles: [ "C", "D" ] },
{ id: 3, component: CustomerDashboard, roles: [ "A", "B" ] },
]
export default function App() {
const condition = 'A';
let dash_role = DashboardComponents.filter(dc => dc.roles.indexOf(condition) > -1);
return dash_role.map(
dr => {
const Comp = dr.component;
return (
<Comp
key={dr.id}
data={`data_props ${dr.id}`}
/>
)
}
)
}
And if you want to use React.cloneElement:
// add onPress for who you want
const ProjectDashboard = ({data, onPress}) => {
return (
<div>
ProjectDashboard: {data}
<button onClick={() => onPress(data)}>Click!</button>
</div>
)
}
export default function App() {
const condition = 'A';
let dash_role = DashboardComponents.filter(dc => dc.roles.indexOf(condition) > -1);
const onPress = data => {
alert(data);
}
return dash_role.map(
dr => {
const Comp = dr.component;
const child = (
<Comp
key={dr.id}
data={`data_props ${dr.id}`}
/>
);
return React.cloneElement(child, { onPress});
}
)
}
As your role "A" have 2 dashboards so I am supposing you only want to access one dashboard at time. So I have written code of it and you have to pass the role name in find function it will fetch the component for it.
import AdminDashboard from "../AdminDashboard";
import ProjectDashboard from "../ProjectDashboard";
import CustomerDashboard from "../CustomerDashboard"
const DashboardComponents = [
{ id: 1, component: AdminDashboard, roles: [ "A" ] },
{ id: 2, component: ProjectDashboard, roles: [ "C", "D" ] },
{ id: 3, component: CustomerDashboard, roles: [ "A", "B" ] },
]
const DashboardComponent = () => {
const find=(role)=>DashboardComponents.filter((item)=>item.roles.includes(role));
return (
<>
{find('A')?.[0]?.component(data)} {/* pass data as props to that component */}
</>
)
}
Related
I have a component like this:
export const MyComponent = props => {
return (
{
props.options.map(option =>
<>
<div>
<input type="radio" id={option.id} name="group" value={option.id} />
<label htmlFor={option.id}>{option.label}</label>
</div>
<span>Some other text</span>
</>
)
}
)
}
And in my test, I want to make sure that both that all the radio buttons are rendered with the right label text and the extra text in the span are present.
import { render, screen, within } from '#testing-library/react'
describe('MyComponent', () => {
const props = {
options: [
{ id: 1, label: 'Apple' },
{ id: 2, label: 'Banana' },
{ id: 3, label: 'Cherry' },
]
}
it('Renders the component', () => {
render(<MyComponent {...props} />)
const options = screen.queryAllByRole('radio')
expect(options).toBeArrayOfSize(3)
options.forEach((option, index) => {
const { getByText } = within(option)
expect(getByText(props.options[index])).toBeInTheDocument() // Assertion fails
expect(getByText("Some other text")).toBeInTheDocument() // Also fails
})
})
})
However, I'm getting errors on the two expect assertions.
You can try the following:
import { render, screen } from "#testing-library/react"
import { MyComponent } from "./MyComponent"
describe("MyComponent", () => {
const props = {
options: [
{ id: 1, label: "Apple" },
{ id: 2, label: "Banana" },
{ id: 3, label: "Cherry" },
],
}
it("Renders the component", () => {
render(<MyComponent {...props} />)
const options = screen.queryAllByRole("radio")
expect(options).toHaveLength(3)
props.options.forEach((option) => {
const label = screen.getByLabelText(option.label)
const radioBtn = screen.getByRole("radio", { name: option.label })
// Need to use getAllByText query since the string "Some other text" is repeated... getByText will throw because of multiple matches
const [someOtherText] = screen.getAllByText("Some other text")
expect(label).toBeInTheDocument()
expect(radioBtn).toBeInTheDocument()
expect(someOtherText).toHaveTextContent(someOtherText.textContent)
})
})
})
Constructor code which contains the data
constructor(props){
super(props);
this.state = {
Data: [
{
"name": 'red'
},
{
"name": 'green'
},
{
"name": 'red'
},
{
"name": 'brown'
},
{
"name": 'yellow'
},
{
"name": 'brown'
}
]
}
}
The code for my button where I map the data
{this.state.Data.map((color) => (
<>
<div className="ViewDetailsBtn"><Button onClick={this.clickMe.bind(this, color.name)} className="ViewDetailsBtnLink">View Details</Button></div></>
))}
onClick function Code
clickMe = (details) => {
console.log(details);
this.props.history.push({
pathname: "/ViewDetails",
state: {detail: details}
});
}
Here it displays the color name on my console properly and it redirects me to ViewDetails but how do I display the color name on the ViewDetails page?
ViewDetails page code
import React from 'react'
const App = (props) => {
console.log(props);
//const data = props.location.state.detail;
return (
<div>
<h1>Details:-</h1>
{/* <h1>{data}</h1> */}
</div>
)
}
export default App
your ViewDetails component should accept a parameter:
const ViewDetails = (props) => {}
then you should be able to access that data through that parameter
I JSON data receive in function params , the data are mapped and display (media table). Each media contains a number of likes (ex: 88). Each time a user clicks on the like it must increase by 1 the value.
Do you have an idea, I would like to do it functionally (no class), Thx.
import React, { useState } from "react";
import _ from 'lodash';
//Data receive in params
const medias = [{
"id": 623534343,
"like": 88,
"date": "2019-02-03"
}, {
"id": 625025343,
"like": 85,
"date": "2019-04-03"
}, {
"id": 2525345343,
"like": 34,
"date": "2019-04-05"
}];
const Test = ({ medias }) => {
//const [items, setItems] = useState([]);
const handleLike = (id) => {
//???
}
return (
<>
{medias.map((media, index) => {
return (
<>
<div key={`galerie-${index}`}>
<p>{media.like}</p>
<button onClick={() => handleLike(media.id)}></button>
</div>
</>
)
})}
</>
);
}
export default Test;
I found the solution to add a system of like incrementation.
Maybe it can help someone
https://codesandbox.io/s/pedantic-fire-mbw7i?file=/src/App.js
import React, { useState, useEffect } from "react";
import _ from "lodash";
const Test = ({ id }) => {
const [medias, setMedias] = useState([]);
useEffect(() => {
fetchMedias();
}, []);
//Get the medias by ID from API
const fetchMedias = async () => {
//const response = await fetch(`api/.../${id}/medias`);
//const data = await response.json();
//Data normally received from API
let mediaJsonExemple = [
{ id: 342550, like: 62, date: "2019-02-07" },
{ id: 8520927, like: 11, date: "2019-03-03" },
{ id: 9025895, like: 72, date: "2019-04-03" }
];
setMedias(_.orderBy(mediaJsonExemple, "date"));
};
const handleLike = (id) => {
const newMedias = [...medias];
const mediasUntouched = newMedias.filter((m) => m.id !== id);
const mediaToUpdate = _.find(newMedias, { id });
mediaToUpdate.like++;
mediasUntouched.push(mediaToUpdate);
const sortedMedia = _.orderBy(mediasUntouched, "date");
setMedias(sortedMedia);
};
let totalLikes = _.sumBy(medias, "like");
return (
<>
<p>Total likes = {totalLikes}</p>
{medias.map((media, index) => {
return (
<>
<div>
<p>{media.like}</p>
<button onClick={() => handleLike(media.id)}>ADD</button>
</div>
</>
);
})}
</>
);
};
export default Test;
I have a simple example where I pass a clickFunction as a value to React Context and then access that value in a child component. That child component re-renders event though I'm using React.memo and React.useCallback. I have an example in stackblitz that does not have the re-render problem without using context here:
https://stackblitz.com/edit/react-y5w2cp (no problem with this)
But, when I add context and pass the the function as part of the value of the context, all children component re-render. Example showing problem here:
https://stackblitz.com/edit/react-wpnmuk
Here is the problem code:
Hello.js
import React, { useCallback, useState, createContext } from "react";
import Speaker from "./Speaker";
export const GlobalContext = createContext({});
export default () => {
const speakersArray = [
{ name: "Crockford", id: 101, favorite: true },
{ name: "Gupta", id: 102, favorite: false },
{ name: "Ailes", id: 103, favorite: true },
];
const [speakers, setSpeakers] = useState(speakersArray);
const clickFunction = useCallback((speakerIdClicked) => {
setSpeakers((currentState) =>
currentState.map((rec) => {
if (rec.id === speakerIdClicked) {
return { ...rec, favorite: !rec.favorite };
}
return rec;
})
);
}, []);
return (
<GlobalContext.Provider
value={{
clickFunction: memoizedValue,
}}
>
{speakers.map((rec) => {
return <Speaker speaker={rec} key={rec.id}></Speaker>;
})}
</GlobalContext.Provider>
);
};
Speaker.js
import React, {useContext} from "react";
import { GlobalContext } from "./Hello";
export default React.memo(({ speaker }) => {
console.log(`speaker ${speaker.id} ${speaker.name} ${speaker.favorite}`);
const { clickFunction } = useContext(GlobalContext);
return (
<button
onClick={() => {
clickFunction(speaker.id);
}}
>
{speaker.name} {speaker.id} {speaker.favorite === true ? "true" : "false"}
</button>
);
});
WORKING CODE BELOW FROM ANSWERS BELOW
Speaker.js
import React, { useContext } from "react";
import { GlobalContext } from "./Hello";
export default React.memo(({ speaker }) => {
console.log(`speaker ${speaker.id} ${speaker.name} ${speaker.favorite}`);
const { clickFunction } = useContext(GlobalContext);
return (
<button
onClick={() => {
clickFunction(speaker.id);
}}
>
{speaker.name} {speaker.id} {speaker.favorite === true ? "true" : "false"}
</button>
);
});
Hello.js
import React, { useState, createContext, useMemo } from "react";
import Speaker from "./Speaker";
export const GlobalContext = createContext({});
export default () => {
const speakersArray = [
{ name: "Crockford", id: 101, favorite: true },
{ name: "Gupta", id: 102, favorite: false },
{ name: "Ailes", id: 103, favorite: true },
];
const [speakers, setSpeakers] = useState(speakersArray);
const clickFunction = (speakerIdClicked) => {
setSpeakers((currentState) =>
currentState.map((rec) => {
if (rec.id === speakerIdClicked) {
return { ...rec, favorite: !rec.favorite };
}
return rec;
})
);
};
const provider = useMemo(() => {
return ({clickFunction: clickFunction});
}, []);
return (
<GlobalContext.Provider value={provider}>
{speakers.map((rec) => {
return <Speaker speaker={rec} key={rec.id}></Speaker>;
})}
</GlobalContext.Provider>
);
};
when passing value={{clickFunction}} as prop to Provider like this when the component re render and will recreate this object so which will make child update, so to prevent this
you need to memoized the value with useMemo.
here the code:
import React, { useCallback, useState, createContext,useMemo } from "react";
import Speaker from "./Speaker";
export const GlobalContext = createContext({});
export default () => {
const speakersArray = [
{ name: "Crockford", id: 101, favorite: true },
{ name: "Gupta", id: 102, favorite: false },
{ name: "Ailes", id: 103, favorite: true },
];
const [speakers, setSpeakers] = useState(speakersArray);
const clickFunction = useCallback((speakerIdClicked) => {
setSpeakers((currentState) =>
currentState.map((rec) => {
if (rec.id === speakerIdClicked) {
return { ...rec, favorite: !rec.favorite };
}
return rec;
})
);
}, []);
const provider =useMemo(()=>({clickFunction}),[])
return (
<div>
{speakers.map((rec) => {
return (
<GlobalContext.Provider value={provider}>
<Speaker
speaker={rec}
key={rec.id}
></Speaker>
</GlobalContext.Provider>
);
})}
</div>
);
};
note you dont need to use useCallback anymore clickFunction
This is because your value you pass to your provider changes every time. So, this causes a re-render because your Speaker component thinks the value is changed.
Maybe you can use something like this:
const memoizedValue = useMemo(() => ({ clickFunction }), []);
and remove useCallback from the function definition since useMemo will handle this part for you.
const clickFunction = speakerIdClicked =>
setSpeakers(currentState =>
currentState.map(rec => {
if (rec.id === speakerIdClicked) {
return { ...rec, favorite: !rec.favorite };
}
return rec;
})
);
and pass this to your provider such as:
<GlobalContext.Provider value={memoizedValue}>
<Speaker speaker={rec} key={rec.id} />
</GlobalContext.Provider>
After providing the answer, I've realized that you are using Context somehow wrong. You are mapping an array and creating multiple providers for each data. You should probably change your logic.
Update:
Most of the time you want to keep the state in your context. So, you can get it from the value as well. Providing a working example below. Be careful about the function this time, we are using useCallback for it to get a stable reference.
const GlobalContext = React.createContext({});
const speakersArray = [
{ name: "Crockford", id: 101, favorite: true },
{ name: "Gupta", id: 102, favorite: false },
{ name: "Ailes", id: 103, favorite: true },
];
function App() {
const [speakers, setSpeakers] = React.useState(speakersArray);
const clickFunction = React.useCallback((speakerIdClicked) => {
setSpeakers((currentState) =>
currentState.map((rec) => {
if (rec.id === speakerIdClicked) {
return { ...rec, favorite: !rec.favorite };
}
return rec;
})
);
}, []);
const memoizedValue = React.useMemo(() => ({ speakers, clickFunction }), [
speakers,
clickFunction,
]);
return (
<GlobalContext.Provider value={memoizedValue}>
<Speakers />
</GlobalContext.Provider>
);
}
function Speakers() {
const { speakers, clickFunction } = React.useContext(GlobalContext);
return speakers.map((speaker) => (
<Speaker key={speaker.id} speaker={speaker} clickFunction={clickFunction} />
));
}
const Speaker = React.memo(({ speaker, clickFunction }) => {
console.log(`speaker ${speaker.id} ${speaker.name} ${speaker.favorite}`);
return (
<button
onClick={() => {
clickFunction(speaker.id);
}}
>
{speaker.name} {speaker.id} {speaker.favorite === true ? "true" : "false"}
</button>
);
});
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root" />
How to add a class to an image if the flag is done: true? As I have not tried, the class is added to all images, and not to those with true...
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [avatarArr, setAvatarArr] = useState({
avatar: [
{
id: 1,
url: "https://starwars-visualguide.com/assets/img/characters/1.jpg"
},
{
id: 2,
url: "https://starwars-visualguide.com/assets/img/characters/2.jpg"
},
{
id: 3,
url: "https://starwars-visualguide.com/assets/img/characters/3.jpg"
}
]
});
const [classUser, setClassUser] = useState(null);
const [selectUser, setSelectUser] = useState(false);
const onAddClass = id => {
if (avatarArr.avatar.find(items => items.id === id)) {
const index = avatarArr.avatar.findIndex(items => items.id === id);
setAvatarArr([
...avatarArr.slice(0, index),
...avatarArr.slice(index + 1)
]);
} else {
setAvatarArr([...avatarArr, { done: true }]);
setSelectUser(avatarArr.avatar.map(items => items.done));
if (selectUser) {
setClassUser("active__user");
}
}
};
const blockCreate = () => {
return avatarArr.avatar.map(items => {
return (
<div key={items.id}>
<img
src={items.url}
alt="avatar"
width="150px"
onClick={() => onAddClass(items.done, items.id)}
className={selectUser ? classUser : null}
/>
</div>
);
});
};
return (
<div className="App">
<div>{blockCreate()}</div>
</div>
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
I'm trying to set true on click, to tell the user that the avatar that was clicked on is selected, and add some kind of styling class.
And if you click a second time, then true - becomes false, in short - the choice
Are you looking like this
export default function App() {
const [avatarArr, setAvatarArr] = useState({
avatar: [
{
id: 1,
url: "https://starwars-visualguide.com/assets/img/characters/1.jpg"
},
{
id: 2,
url: "https://starwars-visualguide.com/assets/img/characters/2.jpg"
},
{
id: 3,
url: "https://starwars-visualguide.com/assets/img/characters/3.jpg"
}
]
});
const [selectUser, setSelectUser] = useState(false);
const onAddClass = item => {
setSelectUser(item);
};
const blockCreate = () => {
return avatarArr.avatar.map(items => {
return (
<div key={items.id}>
<img
src={items.url}
alt="avatar"
width="150px"
onClick={() => onAddClass(items)}
className={selectUser.id === items.id ? "myClass" : ""}
/>
</div>
);
});
};
return (
<div className="App">
<div>{blockCreate()}</div>
</div>
);
}
Live working demo https://codesandbox.io/s/vigilant-almeida-169zj