I have a component which has child components, i want to render these child components with different Ids. They are getting their data from store.The problem is they are rendered but with the same item. how can this be solved?
MultiImages Component
const MultiImages: () => JSX.Element = () => {
const values = ['500', '406', '614'];
return (
<div>
{values.map((val, index) => {
return <OneImage key={index} projectID={val} />;
})}
</div>
);
};
export default MultiImages;
OneImage Component
const OneImage: () => JSX.Element = ({ projectID }) => {
const projectData = useProjectDataStore();
const { getProject } = useAction();
useEffect(() => {
getProject(projectID ?? '');
}, []);
return (
<>
<div>
<img
src={projectData.picture}
}
/>
<div>
<a>
{projectData.projectName}
</a>
</div>
</div>
</>
);
};
export default OneImage;
Your issue here - you are calling in a loop, one by one fetch your projects, and each call, as far as we can understand from your example and comments override each other.
Your are doing it implicitly, cause your fetching functionality is inside your Item Component OneImage
In general, the way you are using global state and trying to isolate one from another nodes is nice, you need to think about your selector hook.
I suggest you, to prevent rewriting too many parts of the code, to change a bit your selector "useProjectDataStore" and make it depended on "projectID".
Each load of next project with getProject might store into your global state result, but instead of overriding ALL the state object, you might want to use Map(Dictionary) as a data structure, and write a result there and use projectID as a key.
So, in your code the only place what might be change is OneImage component
const OneImage: () => JSX.Element = ({ projectID }) => {
// making your hook depended on **projectID**
const projectData = useProjectDataStore(projectID);
const { getProject } = useAction();
useEffect(() => {
// No need of usage **projectID** cause it will inherit if from useProjectDataStore
getProject();
}, []);
return (
<>
<div>
<img
src={projectData.picture}
}
/>
<div>
<a>
{projectData.projectName}
</a>
</div>
</div>
</>
);
};
export default OneImage;
And inside of your useProjectDataStore store result into a specific key using projectID.
Your component OneImage will return what's in the return statement, in your case:
<>
<div>
<img
src={projectData.picture}
/>
<div>
<a>
{projectData.projectName}
</a>
</div>
</div>
</>
This tag <></> around your element is a React.fragment and has no key. This is the reason you get this error.
Since you already have a div tag wrapping your element you can do this:
<div key={parseInt(projectID)}>
<img
src={projectData.picture}
/>
<div>
<a>
{projectData.projectName}
</a>
</div>
</div>
You can also change the key to Math.floor(Math.random() * 9999).
Note that passing the prop key={index} is unnecessary, and is not advised to use index as keys in a react list.
Related
I'm trying to pass in an array of registered users into a component, however I can't because it always renders the initial empty array before actually rendering the correct content. I've tried using useRef and it still does not work.
const Home = () => {
const nav = useNavigate()
const [userList, setUserList] = useState([]);
const [loggedInUser, setLoggedInUser] = useState({});
const [currentChat, setCurrentChat] = useState(undefined);
const [showMenu, setShowMenu] = useState(false);
useEffect(() => {
const setLoggedIn = async() => {
if (!localStorage.getItem('loggedInUser')) {
nav('/');
} else {
setLoggedInUser(await JSON.parse(localStorage.getItem('loggedInUser')))
}
}
setLoggedIn().catch(console.error);
}, [])
useEffect(() => {
const fetchUsers = async () => {
const data = await axios.get(`${allUsersRoute}/${loggedInUser._id}`);
setUserList(data.data);
}
fetchUsers().catch(console.error);
}, [loggedInUser._id])
console.log(userList);
return (
<div id='container'>
<div id='sidebar'>
<div>
<div id='home-header'>
<h1>DevsHelp</h1>
</div>
<Userlist props={userList}/>
</div>
</div>
)};
And here is the component I'm trying to render.
const Userlist = (props) => {
return (
<div>
<div id='home-header'>
<h1>DevsHelp</h1>
</div>
<div id='userlist'>
{props.map((prop) => {
{console.log(props.length)}
return (
<div className='user'>
<h3>{prop.username}</h3>
</div>
)
})}
</div>
</div>
)}
export default Userlist;
So basically, react returns .map is not a function, and I assume it's because the array goes in empty. I'm fairly new to React, so if anyone could help me, I would greatly appreciate it. Thanks!
The problem is that you are mapping over the props object, not the userList.
Try to do the following:
const Userlist = (props) => {
return (
<div>
<div id='home-header'>
<h1>DevsHelp</h1>
</div>
<div id='userlist'>
// use props.users.map instead of props.map
{props.users.map((user) => {
return (
<div className='user'>
<h3>{user.username}</h3>
</div>
)
})}
</div>
</div>
)}
export default Userlist;
and at Home component change the props of UserList to users, just to avoid confusion
<Userlist users={userList}/>
I wouldn't name props for a component "props", really:
<Userlist props={userList}/>
If you really want to, then at least inside Userlist, you would need to refer to the props object:
props.props.map...
Name your props to something that make sense to you, like for example "users". Then call props.users.map(user => {...})
A React component can take many props. When you want to access them inside the component, you need to do so by name. In your case, you defined Userlist like this:
function Userlist(props){...}
In this case, all props would have to be accessed via the props object. You defined a props value inside this object when you called <Userlist props={userList]} />
Personally, I always destructure props when I define a new component, like this:
function Userlist({users}) {...}
As a side note, your code would have worked if you had destructured the props object: function Userlist({props}) {...} This would be the smallest change you could do to make the code work, I guess. But again, I would not use "props" as a name for a prop.
I want to make a component but i want that one accepts elements too. For example :
Component :
const Example = () => {
return (
<div>
//Some Elements will come here.
</div>
)
}
Another Page :
const App = () => {
return (
<Example>
<div>
<h1>Hello all </h1>
<p>I want that elements acceptable on my custom component </p>
</div>
</Example>
)
}
But i only can send props and i cant write anything inside of tags of my component. How can i make it ? Thanks for all!
React defined a special prop called children. That's what you exacly need.
Try like this
const Example = ({ children }) => {
return <div>{children}</div>;
};
const App = () => {
return (
<Example>
<div>
<h1>Hello all </h1>
<p>I want that elements acceptable on my custom component </p>
</div>
</Example>
);
};
You can use props.children
const Example = props => {
return <div>{props.children}</div>;
};
I am creating an application in react that takes output from a mongodb database. In one of the Gallery components, I have placed a map function that displays a list of items. I would like to add an onClick event to each element of the array, which would pass each property to the other component as state.
I have looked for hints in other posts, but none fit my case. I have also tried to solve it myself as far as my skills with react allow me.
What I would like is to pass the individual properties i.e.: file.name and file.cover to the other component.
Gallery Component:
function Gallery() {
const [name, setName] = useState()
const [cover, setCover] = useState()
const video = [...]
const seeDetails = () => {
setName(???)
}
return(
<section>
<ul className='grid grid-cols-2'>
{video.map((file) => (
<li key={file.name} className='relative' onClick={seeDetails}>
<div>
<img
src={file.cover}
alt={file.name}
/>
</div>
<p>
{file.name}
</p>
</li>
))}
</ul>
</section>
<SecondComponent name={name} setName={setName} cover={cover} setCover={setCover} />
)}
SecondComponent:
const SecondComponent = ({ name, setName, cover, setCover }) => {
return (
<p>{name}</p>
<img src={cover} alt={name} />
)
}
seeDetails function doesn't seem to have any provision to be able to access your file object. It be able to accept the file object as a parameter, which I can see from your code snip, contains everything you need to share with SecondComponent
Try:
const seeDetails = (file) => {
setName(file.name);
setCover(file.cover)
}
and you will need it to be explicitly passed through the onClick prop as
<li key={file.name} className='relative' onClick={(event) => seeDetails(file)}>
Also, if it is feasible, instead of creating individual states for name, cover, xyz; would be good to have the file object itself maintained in the state.
I'm trying to create an WithIcon wrapper component which would insert a child (icon) into a wrapped component.
Let's say I have a button:
<Button>Add item</Button>
I want to create a component WithIcon which will be used like this:
<WithIcon i="plus"><Button>Add item</Button></WithIcon>
Ultimately what I want to achieve is this:
<Button className="with-icon"><i className="me-2 bi bi-{icon}"></i>Add item</Button>
Notice the added className and the tag within the Button's body.
I'm trying to figure out how the WithIcon component's code should look like. What is the React way of achieving this result?
The hardest part was the rules of using the WithIcon Will we only have one ?
Will we have only it at the leftmost ? Something like that.
But if we follow your example. We can relatively write something like this for the WithIcon
const WithIcon = ({ i, children }) => {
return React.Children.map(children, (child) => {
return (
<>
<i className={`me-2 bi bi-${i}`}></i>
{React.cloneElement(child, { className: "with-icon" })}
</>
);
});
};
Then we can just use it the way you want it
<WithIcon i="plus"><Button>Add item</Button></WithIcon>
What we do is just looping through the children which in react is any nested jsx you throw in it (Button in our case)
You can find my fiddle here : https://codesandbox.io/s/react-font-awesome-forked-321tz?file=/src/index.js
UPDATE
So my previous answer does not fully meet the end result we want. The will need to be the main parent
The idea is still quite the same as before but here we are infering the type of the component we passed inside the WithIcon This also adds a safeguard when we passed a nested component inside the WithIcon
const WithIcon = ({ i, children }) => {
return React.Children.map(children, (child) => {
const MyType = child.type; // So we can get the Button
return (
<MyType className="with-icon">
<i className={`me-2 bi bi-${i}`}></i>
{(React.cloneElement(child, {}), [child.props.children])}
</MyType>
);
});
};
I think I'll go to sleep I'll update the rest of the explanation at later date.
See the fiddle here :
https://codesandbox.io/s/react-font-awesome-forked-y43fx?file=/src/components/WithIcon.js
Note that this code does not preserved the other props of the passed component, but you can relatively add that by adding {...child.props} at the MyComponent which is just (reflection like?) of infering the component.
Of course also have another option like HOC Enhancers to do this but that adds a bit of complexity to your how to declare your component api. So Pick whats best for ya buddy
Maybe try using a higher order component?
const withIcon = (icon, Component) => ({children, ...props}) => {
return (
<Component className="with-icon" {...props}>
<i className=`me-2 bi bi-${icon}` />
{children}
</Component>
);
}
Then the usage is
const ButtonWithIcon = withIcon("your-icon", Button);
<ButtonWithIcon>Add Item</ButtonWithIcon>
From my experience with react it usually comes down to either using a property inside the component like here (https://material-ui.com/api/button/) or higher order component like what I described.
There are two common patterns used in React for achieving this kind of composition:
Higher-Order Components
Start by defining a component for your button:
const Button = ({ className, children }) => (
<button className={className}>{children}</button>
);
Then the higher-order component can be implemented like this:
const withIcon = (Component) => ({ i, className = '', children, ...props }) => (
<Component {...props} className={`${className} with-icon`}>
<i className={`me-2 bi bi-${i}`} />
{children}
</Component>
);
Usage:
const ButtonWithIcon = withIcon(Button);
<ButtonWithIcon i="plus">Add Item</ButtonWithIcon>
Context
Start by defining the context provider for the icon:
import { createContext } from 'react';
const Icon = createContext('');
const IconProvider = ({ i, children }) => (
<Icon.Provider value={i}>{children}</Icon.Provider>
);
and then your component:
import { useContext } from 'react';
const Button = ({ className = '', children }) => {
const i = useContext(Icon);
if (i) {
className += ' with-icon';
children = (
<>
<i className={`me-2 bi bi-${i}`} />
{children}
</>
);
}
return (
<button className={className}>{children}</button>
);
};
Usage:
<IconProvider i="plus"><Button>Add Item</Button></IconProvider>
I am adding a component onclick and keeping track of the components using useState Array. However when I go to remove one of the added components, it doesn't recognize the full component Array size, only the state that was there when that component was initially added.
Is there a way to have the current state recognized within that delete function?
https://codesandbox.io/s/twilight-water-jxnup
import React, { useState } from "react";
export default function App() {
const Span = props => {
return (
<div>
<span>{props.index}</span>
<button onClick={() => deleteSpan(props.index)}>DELETE</button>
Length: {spans.length}
</div>
);
};
//set initial span w/ useState
const [spans, setSpans] = useState([<Span key={0} index={Math.random()} />]);
//add new span
const addSpan = () => {
let key = Math.random();
setSpans([...spans, <Span key={key} index={key} />]);
};
//delete span
const deleteSpan = index => {
console.log(spans);
console.log(spans.length);
};
//clear all spans
const clearInputs = () => {
setSpans([]);
};
return (
<>
{spans}
<button onClick={() => addSpan()}>add</button>
<button onClick={() => clearInputs()}>clear</button>
</>
);
}
UPDATE - Explaining why you are facing the issue descibed on your question
When you are adding your new span on your state, it's like it captures an image of the current values around it, including the value of spans. That is why logging spans on click returns you a different value. It's the value spans had when you added your <Span /> into your state.
This is one of the benefits of Closures. Every <Span /> you added, created a different closure, referencing a different version of the spans variable.
Is there a reason why you are pushing a Component into your state? I would suggest you to keep your state plain and clean. In that way, it's also reusable.
You can, for instance, use useState to create an empty array, where you will push data related to your spans. For the sake of the example, I will just push a timestamp, but for you might be something else.
export default function App() {
const Span = props => {
return (
<div>
<span>{props.index}</span>
<button onClick={() => setSpans(spans.filter(span => span !== props.span))}>DELETE</button>
Length: {spans.length}
</div>
);
};
const [spans, setSpans] = React.useState([]);
return (
<>
{spans.length
? spans.map((span, index) => (
<Span key={span} index={index} span={span} />
))
: null}
<button onClick={() => setSpans([
...spans,
new Date().getTime(),
])}>add</button>
<button onClick={() => setSpans([])}>clear</button>
</>
);
}
I hope this helps you find your way.