Passing props from Container with fetch to one element via Link - javascript

This is a bit hypotetical situation as i do not have live example. My question is:
I have some random data:
id
name
description
link
photo
github
short description
I have a Projects.js component where I am fetching data
render() {
const { projects } = this.state;
return (
<div className='Projects__Content'>
{projects .map(project => {
return (
<div key={project.id} className='Projects__Project'>
<h3>{project.name}</h3>
<p>{projects.shortDescription}</p>
<Link to=`/project/${project.id}`/>
</div>
);
})}
</div>
);
}
So here in my Projects.js I am rendering all projects that I have in my data, but only I am using some information. From my rendered projects with React Router Link, I want to go to specific project using ID. For that part I have another component Project.js.
Now in Project.js I want to use all data that I fetched in Projects.js, but part is that I am not getting right. Can I pass a props all my fetched data? During my search I found that I should not pass props with Link.
So can I do this like this:
render() {
const { projects } = this.state;
return (
<div className='Projects__Content'>
{projects .map(project => {
return (
<div key={project.id} className='Projects__Project'>
<h3>{project.name}</h3>
<p>{projects.shortDescription}</p>
<Link to=`/project/${project.id}`/>
</div>
);
})}
<Project dataProps={...projects} />
</div>
);
}
But this will render my one project from Project.js, so this is not what I want.
How about this:
render() {
const { projects } = this.state;
return (
<div className='Projects__Content'>
{projects .map(project => {
return (
<div key={project.id} className='Projects__Project'>
<h3>{project.name}</h3>
<p>{projects.shortDescription}</p>
<Link to=`/project/${project.id}` propsData={...projects}/>
</div>
);
})}
</div>
);
}
Or maybe I should use another fetch in Project.js
With redux I can use one reducer to do that, but how this should be handled with React only?
Thanks for help :)

Write Project component which will be used to show the Project details.
The part that is troubling you is that you should have some kind of routing to know which Project to show and you already have link defined, so just add a project route at /project/:projectId.
There are 3 ways that you can share your projects array with project:
Just pass the whole object along you already have in projects component down to project component
Use Redux of some state management to store it globally
Fetch project individually inside project component by project id

Related

React JS Frontend - Key Error When Rendering List

I am getting the following error when rendering a list using ReactJS
react-jsx-dev-runtime.development.js:87 Warning: Each child in a list should have a unique "key" prop.
Check the render method of `DriverList`. See https://reactjs.org/link/warning-keys for more information.
at DriverList (http://localhost:3000/static/js/bundle.js:720:13)
at div
at Drivers (http://localhost:3000/static/js/bundle.js:1401:75)
at Routes (http://localhost:3000/static/js/bundle.js:47364:5)
at Router (http://localhost:3000/static/js/bundle.js:47297:15)
at BrowserRouter (http://localhost:3000/static/js/bundle.js:46106:5)
at App
So far I have tried to unsuccessfully resolve it by adding the key value to both the Driver, a higher level div than the Driver object that doesn't need to be there, adding a key to the a React Fragment wrapped around the Driver object. I have tried adding it to each of them individually as well as multiple parts. I have also tried adding the key to the li item within the actual Driver object file that populates the DriverList. I still keep getting the error and I am thinking I am making a stupid mistake somewhere along the code that someone can point out and laugh at me for.
import React from "react"
import Driver from "./Driver"
import Card from "../../shared/components/interface/Card"
import "./DriverList.css"
const DriverList = (props) => {
if (props.items.length === 0) {
return (
<Card key='none'>
<h2>No drivers found.</h2>
</Card>
)
} else {
// Render the List
return (
<ul className='driver-list'>
{props.items.map((driver) => {
return (
<React.Fragment key={driver.id}>
<Driver
key={driver.id}
id={driver.id}
permitno={driver.permitno}
fname={driver.fname}
lname={driver.lname}
mailaddr1={driver.mailaddr1}
mailaddr2={driver.mailaddr2}
dateofbirth={driver.dateofbirth}
driversex={driver.driversex}
licenseidno={driver.licenseidno}
dvrclass={driver.dvrclass}
dvrpermit={driver.dvrpermit}
endorsemnt={driver.endorsemnt}
restricts={driver.restricts}
expdate={driver.expdate}
cmpldate={driver.cmpldate}
numpoints={driver.numpoints}
suspended={driver.suspended}
driver_status={driver.driver_status}
/>
</React.Fragment>
)
})}
</ul>
)
}
}
export default DriverList
try:
props.items.map((driver, idx) => {
return (
<Driver
key={idx}
and see if it helps. And if it does it means that driver.id repeats somewhere

Trouble iterating/mapping props

I'm trying to develop a simple application in which any number of tasks will be rendered as cards. I'm passing them as props, schemed like so:
taskList: [{
taskID: 1,
taskTitle: 'Task 1',
taskDescription: 'Description 1',
completed: true
}]
By logging the props in the TaskCard component, I can see the list arrives exactly like that. If I try and log something such as props[0].taskDescription, it'll successfully return "Description 1", like so:
export default function TaskCard(props) {
return(
<div className="task-card" draggable>
<h3> Test </h3>
{ props[0].taskDescription } // this actually works
</div>
)
}
I can't, however, map or calculate the length of the props to iterate through props.
What am I doing wrong in terms of iteration? Is it a flawed architecture in terms of componentization?
To render a list of TaskCards, you need to do the mapping of taskList outside that component like so:
{taskList.map(task => <TaskCard task={task} />)}
and then TaskCard would render using the passed task props:
TaskCard(props) {
// use props.task.taskDescription, etc.
}
First, thank you all who contributed with your insights in the comments for the original posting. So yes, apparently passing an array as a prop is troublesome. I may not have resolved in the most efficient way, but here's what it has come down to so far. Use the image in the original post to guide yourself.
I have resolved the issue the following way:
UserArea.jsx
Contains a state that logs the user, username, a list containing pending tasks pendingList and a list containing complete tasks completedList;
It renders a header, the PendingArea and CompleteArea component passing {...this.state} to each of them (the full component is an object).
render(){
return(
<div className="user-area-container">
<div className="profile-header">
<h2>{ this.state.userName }</h2>
{ this.state.email }
</div>
<PendingArea {...this.state} />
<CompletedArea {...this.state} />
</div>
)
}
PendingArea.jsx and CompleteArea.jsx
Here I made the filtering, passing only the equivalent mapping to each of the components. The code for the PendingArea component is as follows:
function PendingArea(props) {
var pendingTasks = props.pendingList.map(task => {
return <TaskCard {...task} />
});
return(
<div className="status-container">
<div className="status-title">
<h2>Pending tasks</h2>
</div>
<div className="status-modifier">
{ pendingTasks }
</div>
</div>
)
}
TaskCards.jsx
Finally, the TaskCard component uses the direct properties from the props:
export default function TaskCard(props) {
return(
<div className="task-card" draggable>
<h3> { props.taskTitle } </h3>
{ props.taskDescription }
</div>
)
}
This way I managed to properly render the task card in their due place. I hope this works for anyone reading this, as well.

How to re-render a component with React-Router <Link> pointing to the same URL

To keep it simple, the detail page fetches data on mount based on the movie ID in the URL, this coming from path='movie/:id' in the Route.
It's child is called Recommended, which shows you recommended movies based again on the current URL.
class MovieDetailPage extends React.Component {
// Fetch movies and cast based on the ID in the url
componentDidMount() {
this.props.getMovieDetails(this.props.match.params.id)
this.props.getMovieCast(this.props.match.params.id)
}
render() {
<div>
Movies here
</div>
<Recommended id={this.props.match.params.id}/>
}
}
The Recommended component fetches data based on the current movie as well and generates another tag pointing to another movie.
class Recommended extends React.Component {
componentDidMount() {
this.props.getRecommended(this.props.id)
}
render() {
return (
<>
<Category title={'Recommended'}></Category>
<div className="movies">
{
this.props.recommended.map((movie) => {
return (
<Link key={movie.id} to={`movie/${movie.id}`} className="movies__item">
<img
key={movie.id}
src={`https://image.tmdb.org/t/p/w342${movie.poster_path}`}
className="movies__item-img"
alt={`A poster of ${movie.title}`}
>
</img>
</Link>
)
})
}
</div>
</>
)
}
}
Now how can I trigger another render of the parent component when clicking the Link generated in the Recommended component? The URL is changing but this won't trigger a render like I intent to do.
UPDATE:
<Route
path="/movie/:id"
render={(props) => (
<MovieDetailPage key={props.match.params.id}
{...props}
)}
/>
I passed in a unique key this time that triggered the re-render of the page. I tried this before but I might've screwed up the syntax.
This post got me in the right direction: Force remount component when click on the same react router Link multiple times
Add a key to the page
If you change route but your page is not getting its "mount" data then you should add a key to the page. This will cause your page to rerender and mount with the new id and get the data again.
You can read more about react keys here
A key tells react that this is a particular component, this is why you see them in on lists. By changing the key on your page you tell react that this is a new instantiation of the component and has changed. This will cause a remount.
Class component example
class MyPage extends React.Component {
componentDidMound() {
// this will fire each time the key changes since it triggers a mount
}
render() {
return (
<div key={props.pageId}>
{/* component stuff */}
</div>
)
}
}
Functional component example
const MyPage = (props) => {
React.useEffect(() => {
// this will fire each time the key changes
}, []);
return (
<div key={props.pageId}>
{/* component stuff */}
</div>
)
}
You can add another React lifecycle method that triggers on receiving new props (UNSAFE_componentWillReceiveProps, componentDidUpdate, getDerivedStateFromProps) in your Recommended component like this:
UNSAFE_componentWillReceiveProps(nextProps) {
if (nextProps.id !== this.props.id) {
nextProps.getRecommended(nextProps.id);
};
}
You can also add key to your component (which forces it to re-render completely if key changed) like this:
<Recommended key={this.props.match.params.id} id={this.props.match.params.id}/>
You can also use React Hooks to handle this more easily with useEffect:
const Recommended = (props) => {
const { id, getRecommended, recommended } = props;
useEffect(() => {
id && getRecommended(id);
}, [id]);
return (
<>
<Category title={'Recommended'}></Category>
<div className="movies">
{recommended.map((movie) => {
return (
<Link key={movie.id} to={`movie/${movie.id}`} className="movies__item">
<img
key={movie.id}
src={`https://image.tmdb.org/t/p/w342${movie.poster_path}`}
className="movies__item-img"
alt={`A poster of ${movie.title}`}
></img>
</Link>
);
})}
</div>
</>
);
};
Note: adding key to component and complete its re-render is not best practice and you should be using Component's lifecycles to avoid it if possible

Update data in parent when added in child in react

I'm new to React. I see a lot of posts for updating children when the parent is updated but I haven't found the opposite.
I have a parent component (MainComponent) fetching and storing data. The form to add data (FormNewSubItem ) is in a child component.
When I submit the form I add a subitem in an item and I want the array of items in main component to store the new data.
In MainComponent.jsx
storeItems(items) {
// store items in db
}
retrieveItems() {
// return items from db
}
render {
return (
<MainComponent>
<ListItems items={this.retrieveItems()} />
</MainComponent>
)
}
In ListItems.jsx
render {
return (
<div>
{this.props.items.map((item, idx) => <Item item={item} key={idx} />)}
</div>
)
}
In Item.jsx
handleNewSubItem = (subItem) => {
this.props.item.addSubItem(subItem);
// how to update the 'fetchedItems' in MainComponent ?
}
render {
return (
<React.Fragment>
<div className="title">{this.props.item.title}</div>
<div className="content">
<ListSubItems subitems={this.props.item.subitems}>
<FormNewSubItem handleNewSubItem={thid.handleNewSubItem} />
</ListSubItems>
</div>
</React.Fragment>
)
}
So there are a few ways you can do this. A common way would to connect both components to the same set of data using a library like Redux.
But if you just want to do this in React, then you will need to define the function in the top level component and then pass it down as props to the child component.
So the MainComponent.js will have a function like
addSubItem = (item) => {
// update the state here
}
Then you will pass it down as props to the component you want to use it in, so pass it to the ListItems.js component by
<ListItems items={this.retieveItems()} addSubItem={addSubItem} />
and then again to the Item.js component
<Item item={item} key={idx} addSubItem={this.props.addSubItem}/>
then in the Item component just call it with this.props.addSubItem
Because it is scoped to the top level component, when you call the function in the child components and pass it an item, it will update the state in the parent component
Look into using Context and the useContext hook if you don’t want to use redux for a simple app.

Add AddThis to React component

I have a blog implementation based on ReactJS that I would like to integrate with AddThis. I have my social icons and I want to use them. So I'm looking for a way to integrate just the AddThis backend service.
I tried looking around but I was not able to find how to integrate AddThis to a ReactJS component.
I saw this somewhere and it uses a special namespace which to the best of my knowledge is not react friendly.
<div addthis:url='blog_url' addthis:title='blog_title' class="addthis_toolbox">
<a class="addthis_button_facebook">
<svg ... />
</a>
<a class="addthis_button_twitter">
<svg ... />
</a>
<a class="addthis_button_linkedin">
<svg ... />
</a>
<a class="addthis_button_reddit">
<svg ... />
</a>
</div>
<script type="text/javascript" src="http://s7.addthis.com/js/250/addthis_widget.js#pubid=xa-4fc9383e1ee05f1b"></script>
Also, I saw this JSFiddle with some information on it, but it is not using ReactJS and does not use custom icons.
Question: Is there any good documentation around AddThis + React?
In addition to the data attribute changes you should use the addthis.layers.refresh() method to dynamically refresh/load your AddThis components:
render() {
return (
<div className="addthis_inline_share_toolbox"
data-url={this.props.myurl}
data-title="Check out this URL"
>
</div>
);
}
Then in componentDidMount():
componentDidMount() {
addthis.layers.refresh();
}
EDIT: The above method is the initial approach i took and does initialise the add this widget however, the widget seems to not update the data-url when the prop is changed. even if i call addthis.layers.refresh(); again after a props update
Dynamic update solution:
In my render method:
// Don't use data attributes
<div className="addthis_inline_share_toolbox"></div>
Use the lifecycle methods:
componentDidMount() {
addthis.layers.refresh(); // important! init the add this widget
addthis.update('share', 'url', 'my-initial-url'); // update with initial prop value
}
componentDidUpdate() {
addthis.update('share', 'url', this.props.myurl); // update with prop value
}
componentWillReceiveProps(nextProps) {
addthis.update('share', 'url', nextProps.myurl); // update with prop value
}
Replace addthis:url and addthis:title with data-addthis-url and data-addthis-title.
I put this div in to display the addthis buttons.
<div className="addthis_inline_share_toolbox" data-url={ `http://[Your URL]` } data-title={ `[Your Title]` }></div>
But I also needed to load the javascript after the component mounted or the buttons never display. I assume if you add the javascript to your template that it's loading before the share_toolbox is loaded.
componentDidMount() {
setTimeout( () => {
var addthisScript = document.createElement('script');
addthisScript.setAttribute('src', 'http://s7.addthis.com/js/300/addthis_widget.js#pubid=[your id here]')
if (document.body) document.body.appendChild(addthisScript)
});
},
Here is how I did it:
Please, note that I'm using the inline share toolbox.
Thanks #Mark for addthis.update and to #jvoros for react-load-script
import React, { useEffect } from 'react';
import Script from 'react-load-script';
const AddThis = (props) => {
useEffect(() => {
if (window.addthis) {
window.addthis.update('share', 'url', props.url);
}
}, [props.url]);
const handleAddthisLoaded = () => {
window.addthis.init();
window.addthis.update('share', 'url', props.url);
};
return (
<>
<div className="addthis_inline_share_toolbox"></div>
<Script
url="//s7.addthis.com/js/300/addthis_widget.js#pubid=ra-xxxxxxxxxxxxxxxx"
onLoad={handleAddthisLoaded} />
</>
);
}
export default AddThis;
This was the only info I could find on implementing AddThis in a React app. Eventually helped me get a fix. I am posting my solution for anyone else who comes across this.
Using React Router and AddThis presented some challenges. The key was attaching the addThis javascript methods to window events and not React lifecycle events.
I used react-load-script to asynchronously load the script on the main page of my app, and implemented a callback to initialize the addThis widget and then set state to indicate if addThis was loaded. Then that state gets passed down to the component.
Partial code:
import * as LoadScript from 'react-load-script';
class App extends React.Component {
constructor(props) {
this.state = { addThisLoaded: false }
}
handleScriptLoad() {
this.setState({ addthisLoaded: true });
window.addthis.init();
}
render() {
return (
<LoadScript
url="http://s7.addthis.com/js/300/addthis_widget.js#pubid=ra-xxxxxxxxxxxxxxxx"
onLoad={this.handleScriptLoad.bind(this)}
/>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/page/:id"
render={routeProps => (<Page {...routeProps} addThisLoaded={this.state.addThisLoaded} />)}
/>
</Switch>
);
}
}
Then in the component that implements the addThis widget I attached the window event listeners to the React lifecycle hooks. The addThisLoaded prop can be used to conditionally render the widget.
export default class Page extends React.Component<Props> {
componentDidMount() {
window.addEventListener('load', window.addthis.layers.refresh());
}
componentWillUnmount() {
window.removeEventListener('load', window.addthis.layers.refresh);
}
render() {
const page_url = `YOUR URL GOES HERE`;
const page_title = `YOUR TITLE GOES HERE`;
return (
<div>
{this.props.addThisLoaded === true && (
<div className="addthis_inline_share_toolbox" data-url={page_url} data-title={page_title} />
)}
</div>
);
}
}
If there is a better way to handle it I'd love to hear. The AddThis API docs are sparse. And the fact that it manipulates the DOM to get the desired behavior makes it tricky to incorporate with React Router.
Replace addthis:url with data-url

Categories

Resources