I am not sure how to seperate my data from my components in complex circumstances:
Imagine I am building a tabbed video game container that has an API like this:
const App = props =>
<VideoGameContainerGroup tabbed searchable>
<VideoGame id="cool-game-22" name="Cool Game, part 22" genre="RTS">
<CoolVideoGame /> {/* Implementation goes here */}
</VideoGame>
<VideoGame id="something-wild" name="Craziest game ever" genre="button">
<div>
<h1>Some inline defined game-like component</h1>
<button>click me</button>
</div>
</VideoGame>
{/* ... more games .. */}
</VideoGameContainerGroup>
It needs to not know about the game implementation, but wants to label the games somehow so it can index them, perform search and some other UI stuff:
class VideoGame extends component {
getDynamicBio() {
return `Hi, I am game ${this.props.name} fom genre ${this.props.genre}`;
}
render() {
<div className="some-VideoGameContainer-boilerplate">
{this.props.children /* implementation */}
</div>
}
}
class VideoGameContainerGroup extends Component {
render() {
return (
<div>
<div className="all-games">
{this.props.children}
</div>
{/* ... code, which through sub componentes resolve to: */}
<div className="SearchableGamesTabHeader">
{React.Children.map(this.props.children, (game, i) => {
{ /* but here I need access to complex class methods */}
<span>Game bio: {game.getDynamicBio()}</span>
{ /* this won't work */ }
})}
</div>
</div>
)
}
}
I have considered exporting a bag of helper functions, for instance:
const getDynamicBio = (component) =>
`Hi, I am game ${component.props.name} fom genre ${component.props.genre}`
But the methods are coupled to the VideoGame container and its data, and it seems weird to approach it this way.
I have considered doing something like:
<VideoGame videoGameData={ new VideoGameData({ id: 'cool-game-22', genre: 'RTS' }) }>
<GameImplementation />
</VideoGame>
And then accessing the data through VideoGame.props.videoGameData. But my insticts tell me this is really wrong.
VideoGame really is the component that contains all that meta data, from an OO approach I would store my methods related to that on that class. How would I solve this in a maintainable way in React?
We dont access child component methods, in parent, directly, in react. If you want that, do something like this.
class VideoGameContainerGroup extends Component {
render() {
return (
<div>
<div className="all-games">
{this.props.children}
</div>
{/* ... code, which through sub componentes resolve to: */}
<div className="SearchableGamesTabHeader">
{React.Children.map(this.props.children, (ChildEl, i) => {
<ChildEl complexMethodAsCb = {()=>//pass in particular work you need to do say, on click} neededPropsInComplexMethod={...}/>
<span>Game bio: {game.getDynamicBio()}</span>
</ChildEl/>
})}
</div>
</div>
)
}
}
So we trickle data and methods from top in react. We pass whatever known data and methods we have in parent, to children and children call those, after/before there specific methods.
Further, we also try to remove all data/methods out of components, in react. This is where a pattern called flux comes in. You may check redux to further read and simplify this.
Related
I'm new to programing and I don't know what's the best approach here. I made a StudentCard and I have the mapping in this component like this:
<ul className="-my-5 divide-y divide-slate-200">
{props.binderMemberships.map((binderMembership) => (
<li key={binderMembership.id} className="py-4">
<div className="flex items-center space-x-4">
<div className="flex-shrink-0">
<Avatar of={binderMembership.binder.student} size="8" />
</div>
</div>
</li>
</ul>
Now I want the Avatar be separate from this, so I made another child component such as:
import Avatar from "./Avatar"
import type { Binder, BinderMember, User } from "#prisma/client"
export interface AvatarLoaderProps {
binderMemberships: (BinderMember & { binder: Binder & { student: User } })[]
user: User
}
export function AvatarLoader(props: AvatarLoaderProps) {
return (
<div className="flex-shrink-0">
<Avatar of={props.binderMemberships.binder.student} size="8" />
</div>
)
}
Here is my issue: I need to pass the binderMembership that is given to me by mapping in the parent component but I need it in the child component. how can I pass it as a props since it's just an item of items and is just a name?
First of all, you forgot to close the tags of mapping. After </li> it should have )}.
Second, you can pass props to nested components or put it higher, which is simple to do. You want to pass to nested, so it must declare this in your component. I don't know if you have a class/functional component, a classic function or an arrow. But it should look like this:
function NameOfFunction ({ol}) { }
I dispatched props because it is easier to use it.
Third is to use the value somewhere you want using handlebars {} i.e. {props.id}.
And that's all.
New to Vue and JS. I have a vue page myLists which takes an array of lists (containing media IDs for a title) which I used to make axios API calls and build a carousel (using vue3-carousel package) in the child with the return data sent as a prop. I'm currently dealing with a "Maximum recursive updates exceeded in v-for component " warning that I believe has to do with how I make my API calls. Here is the relevant code below:
Parent "myLists", has multiple lists (each list has movies) and fetches data from api using axios:
<template>
<div v-if="isReady">
<List v-for="list of theList" :key="list.id" :aList="list"></List>
</div>
</template>
export default {
setup() {
const theList = ref([]);
for (var myList of myLists) {
const aList = ref([]);
for (var ids of myList) {
var apiLink = partLink + ids;
axios.get(apiLink).then((response) => {
aList.value.push({
title: response.data.original_title || response.data.name,
overview: response.data.overview,
url: "https://image.tmdb.org/t/p/w500" + response.data.poster_path,
year: response.data.first_air_date || response.data.release_date,
type: ids[1],
id: response.data.id,
});
});
}
theList.value.push(aList.value);
}
return { theList };
},
computed: {
isReady() {
//make sure all lists are loaded from the api before rendering
return this.theList.length == myLists.length;
},
},
Child component "List" (not including script tag as I don't think it's too relevant) takes the fetched data as a prop and builds a carousel with it:
<template>
<Carousel :itemsToShow="4.5" :wrapAround="true" :breakpoints="breakpoints">
<Slide v-for="slide of aList" :key="slide">
<img
#click="showDetails"
class="carousel__item"
:src="slide.url"
alt="link not working"
:id="slide"
/>
</Slide>
<template #addons>
<Navigation />
<Pagination />
</template>
</Carousel>
</template>
Don't know what's causing the error exactly. I have a feeling it could be how I do all my API calls, or maybe it's something else obvious. Anyone have a clue?
The fix was actually absurdly simple, I was on the right track. The component was trying to render before my data was ready, so simply adding a
"v-if="list.length!==0"
directly on my Carousel component (within my List component), as opposed to the parent, fixed my issue.
I assumed that props are automatically ready when passed to a child if I used a v-if in my parent, turns out I was wrong, and that there's a delay.
Found the solution on the github issues page:
https://github.com/ismail9k/vue3-carousel/issues/154
Using React with TypeScript, there are several ways to define the type of children, like setting it to JSX.Element or React.ReactChild or extending PropsWithChildren. But doing so, is it possible to further limit which particular element that React child can be?
function ListItem() {
return (
<li>A list item<li>
);
}
//--------------------
interface ListProps {
children: React.ReactChild | React.ReactChild[]
}
function List(props: ListProps) {
return (
<ul>
{props.children} // what if I only want to allow elements of type ListItem here?
</ul>
);
}
Given the above scenario, can List be set up in such a way that it only allows children of type ListItem? Something akin to the following (invalid) code:
interface ListProps {
children: React.ReactChild<ListItem> | React.ReactChild<ListItem>[]
}
You can't constrain react children like this.
Any react functional component is just a function that has a specific props type and returns JSX.Element. This means that if you render the component before you pass it a child, then react has no idea what generated that JSX at all, and just passes it along.
And problem is that you render the component with the <MyComponent> syntax. So after that point, it's just a generic tree of JSX nodes.
This sounds a little like an XY problem, however. Typically if you need this, there's a better way to design your api.
Instead, you could make and items prop on List which takes an array of objects that will get passed as props to ListItem inside the List component.
For example:
function ListItem({ children }: { children: React.ReactNode }) {
return (
<li>{children}</li>
);
}
function List(props: { items: string[] }) {
return (
<ul>
{props.items.map((item) => <ListItem>{item}</ListItem> )}
</ul>
);
}
const good = <List items={['a', 'b', 'c']} />
In this example, you're just typing props, and List knows how to generate its own children.
Playground
Here's a barebones example that I am using for a "wizard" with multiple steps. It uses a primary component WizardSteps (plural) and a sub-component WizardStep (singular), which has a "label" property that is rendered in the main WizardSteps component. The key in making this work correctly is the Children.map(...) call, which ensures that React treats "children" as an array, and also allows Typescript and your IDE to work correctly.
const WizardSteps: FunctionComponent<WizardStepsProps> & WizardSubComponents = ({children}) => {
const steps = Children.map(children, child => child); /* Treat as array with requisite type */
return (
<div className="WizardSteps">
<header>
<!-- Note the use of step.props.label, which is properly typecast -->
{steps.map(step => <div className="WizardSteps__step">{step.props.label}</div>)}
</header>
<main>
<!-- Here you can render the body of each WizardStep child component -->
{steps.map(step => <div className="WizardSteps__body">{step}</div>)}
</main>
</div>
);
}
const Step: FunctionComponent<WizardStepProp> = ({label, onClick}) => {
return <span className="WizardSteps__label">
{label}
</span>
}
WizardSteps.Step = Step;
type WizardSubComponents = {
Step: FunctionComponent<WizardStepProp>
}
type WizardStepsProps = {
children: ReactElement<WizardStepProp> | Array<ReactElement<WizardStepProp>>
};
type WizardStepProp = {
label: string
onClick?: string
children?: ReactNode
}
Absolutely. You just need to use React.ReactElement for the proper generics.
interface ListItemProps {
text: string
}
interface ListProps {
children: React.ReactElement<ListItemProps> | React.ReactElement<ListItemProps>[];
}
Edit - I've created an example CodeSandbox for you:
https://codesandbox.io/s/hardcore-cannon-16kjo?file=/src/App.tsx
I am new to react and this is my first site and I have some plan JavaScript I found online that will allow a word to be typed out and updated over a course of time. I have already made it into a react Component. But I am not sure how to covert this JavaScript Function into react code.
Here is my new React Component.
import React, {Component} from 'react';
class Hero extends Component {
render () {
return (
<div>
<section id="hero" className="d-flex flex-column justify-content-center align-items-center">
<div className="hero-container" data-aos="fade-in">
<h1>Augusto J. Rodriguez</h1>
<p>I'm a <span className="typed" data-typed-items="opption1, opption2, opption3, opption4"></span></p>
</div>
</section>
</div>
);
}
}
export default Hero;
Here is the vanilla JavaScript that I want to use in my code. Currently this is living in my main.js file that is being called from the index.html. this is the only part of the code that is not working.
if ($('.typed').length) {
var typed_strings = $('.typed').data('typed-items');
typed_strings = typed_strings.split(',')
new Typed('.typed', {
strings: typed_strings,
loop: true,
typeSpeed: 100,
backSpeed: 50,
backDelay: 2000
});
}
I am assuming I need to create a function where my tag is. But I am not sure how to do that in React.
any article references would be awesome or tips on how to resolve this. I have the full code for this project on GitHub.
Looks like that piece of code is using a library called Typed.js.
From looking at your project, I see you setup the Typed.js library inside your public/assets/vendor folder. Instead I would recommend using the NPM package manager to install and setup Typed.js, and copy over the code to work the React way. https://github.com/mattboldt/typed.js/.
Here's an example using Typed.js with React. https://jsfiddle.net/mattboldt/ovat9jmp/
class TypedReactDemo extends React.Component {
componentDidMount() {
// If you want to pass more options as props, simply add
// your desired props to this destructuring assignment.
const { strings } = this.props;
// You can pass other options here, such as typing speed, back speed, etc.
const options = {
strings: strings,
typeSpeed: 50,
backSpeed: 50
};
// this.el refers to the <span> in the render() method
this.typed = new Typed(this.el, options);
}
componentWillUnmount() {
// Make sure to destroy Typed instance on unmounting
// to prevent memory leaks
this.typed.destroy();
}
render() {
return (
<div className="wrap">
<h1>Typed.js</h1>
<div className="type-wrap">
<span
style={{ whiteSpace: 'pre' }}
ref={(el) => { this.el = el; }}
/>
</div>
<button onClick={() => this.typed.toggle()}>Toggle</button>
<button onClick={() => this.typed.start()}>Start</button>
<button onClick={() => this.typed.stop()}>Stop</button>
<button onClick={() => this.typed.reset()}>Reset</button>
<button onClick={() => this.typed.destroy()}>Destroy</button>
</div>
);
}
}
ReactDOM.render(
<TypedReactDemo
strings={[
'Some <i>strings</i> are slanted',
'Some <strong>strings</strong> are bold',
'HTML characters × ©'
]}
/>,
document.getElementById('react-root')
);
It looks like currently you're using the 'typed' library to create this typed list. There are some community packages that act as React wrappers for that, like this one:
https://www.npmjs.com/package/react-typed
Alternatively, you could do what that library does yourself, by loading the 'typed' package in a call to componentDidMount, passing in a React ref instead of a DOM element.
By the way, currently your code uses jQuery (assigned to the variable $) so it's not quite vanilla JS. You could replace the calls to $ with calls to document.querySelector, to make this vanilla JS (though your code might depend on jQuery elsewhere)
I'm trying to have a component that can find parent/child components of the same type.
These components will also have to be "repeatable" programmatically.
Such that:
<MyComponent id="1>
<div>
<MyComponent id="2">
<div>
<MyComponent id="3"></MyComponent>
</div>
<div>
<MyComponent id="4"></MyComponent>
</div>
</MyComponent>
</div>
</MyComponent>
Basically what I need, is a way to traverse the tree of MyComponents (traversal is logically controlled).
I can pass control/parameters to the parent component, or have the parent component pass control/parameters to children components in a predefined order (based on data).
I have two methods to do this, both involve preprocessors.
One is a preprocessor that generates a new Component for each MyComponent found, with some boilerplate. Something like:
var MyComponent_1 = React.createClass({
initialState: function(){ return {currentObject: 0} },
parentCallback: function(x){ /* traversal logic */ },
render: function(){
var nodes=[];
for(var i=0;i<RepeatParam;i++) nodes.push((<div><MyComponent_2 parent={parent}></MyComponent_2></div>));
return nodes;
}
});
var MyComponent_2 /** just like the above */
Another method was to add function closures, something like this:
var $parent=0, parent=0;
<div>
(function(){parent=$parent;$parent=1;
return (<MyComponent parent={parent}>
<div>
(function(){parent=$parent;$parent=2;
<MyComponent parent={parent}></MyComponent>
})()
</div></MyComponent>}))()</div>
Yet another method was to use global variables and inject them into the createClass.
All of these methods seem wrong, and as if I have a very big misunderstanding of how React should work. Is there a more elegant way to be able to traverse the tree of components, and what is the anti-pattern I am committing; how should I be doing this?
This can now be done using the "context" api
export class Hierarchy {
contextTypes: {
level: React.PropTypes.number
}
childContextTypes: {
level: React.PropTypes.number
}
getChildContext() {
return {
level: this.context.level + 1
};
}
render() {
return <div>{this.context.level}{this.children}</div>;
}
}
A combination of higher order components, contexts, and Flux makes it easier to implement.