How to pass Mobx store as props to react compoent - reactjs

I have this app that uses mobx, in it there is a component called "Listings" that uses some state from mobx to render a list of items.
The way it is right now, is that the Listings component gets the data it needs(store.restaurantResults[store.selectedFood]) from inside of it by using the mobx store like so:
const Listings = () => {
const store = React.useContext(StoreContext);
return useObserver(() => (
<div className="pa2">
{store.restaurantResults[store.selectedFood] &&
store.restaurantResults[store.selectedFood].map((rest, i) => {
return (
<div key={i} className="pa2 listing">
<p>{rest.name}</p>
</div>
);
})}
</div>
));
};
But i think this is wrong, as it couples the component with the data, I want instead to pass that data via props so it can be reusable.
What is the correct way to do this? Right now my App looks like this, where it's being wrapped around a storeProvider:
function App() {
return (
<StoreProvider>
<div className="mw8 center">
<Header title="EasyLunch" subTitle="Find Pizza, Burgers or Sushi in Berlin the easy way"/>
<FixedMenu menuItem1={"Pizza"} menuItem2={"Burger"} menuItem3={"Sushi"} />
<p className="b tc pt3">or...</p>
<Search />
<Listings />
</div>
</StoreProvider>
);
}
My idea is to extract everrything inside the StoreProvider into another component that has a store and returns the jsx via useObserver so that I can acces the store and then pass what i need as props to the other components. like this:
const Wapper = () => {
const store = React.useContext(StoreContext);
return useObserver(() => (
<div className="mw8 center">
<Header title="EasyLunch" subTitle="Find Pizza, Burgers or Sushi in Berlin the easy way" />
<FixedMenu menuItem1={"Pizza"} menuItem2={"Burger"} menuItem3={"Sushi"} />
<p className="b tc pt3">or...</p>
<Search />
<Listings listings={store.restaurantResults[store.selectedFood]} />
</div>
))
}
And then on the listings component change the hard coded store.restaurantResults[store.selectedFood] inside to use the props that is being passes now, that is called listigs like so:
const Listings = ({listings}) => {
const store = React.useContext(StoreContext);
return useObserver(() => (
store.loading
? <Loading />
: <div className="pa2">
<div className="flex flex-wrap">
{listings &&
listings.map((rest, i) => {
return (
<div key={i} className="pa2 listing">
<img className='object-fit' src={rest.image_url} alt="restuarant" />
<p>{rest.name}</p>
<p>{rest.location.address1}</p>
</div>
);
})}
</div>
</div>
));
};
And this works, but is this the right way to go about this?

As <Listings/> can be provided with listing and loading you can:
const Listings = ({listings, loading}) => {
if(loading) return <Loading />
return (
<div className="pa2">
<div className="flex flex-wrap">
{listings && listings.map((rest, i) => {
return (
<div key={i} className="pa2 listing">
<img className='object-fit' src={rest.image_url} alt="restuarant" />
<p>{rest.name}</p>
<p>{rest.location.address1}</p>
</div>
);
})}
</div>
</div>
);
}
No observables used, no useObservable required.
You want to useObservables on store for listings then no reason to wrap all components with useObservable. You should wrap <Listings/> only.

I usually define my store as a global, so every component has visibility of it:
class Store {
#observable myVar
}
global.store = new Store()
And in my components i just use it:
#observer
export default class MyComponent extends React.Component {
constructor () {
super()
store.myVar = 0
}
setMyVar (a) {
store.myVar += 1
}
render () {
return <button onClick={this.setMyVar}>
Clicked {store.myVar} times
</button>
}
}

Related

How to pass prop of a child to another child? (React Hooks)

I need to pass a prop (it's a unique id) from one child component to another, but I can't do it.
The operation is simple: Homepage is the parent and has two direct children: Gallery and Detail. Gallery using a map prints its children Cards, which have a unique id. I need that when clicking on a card button, that unique id can be received by Detail.
I tried to manage it through handleClick but since I already manage a state there, I don't know if it is possible to manage it from there or are other methods.
This is the code:
interface DetailProps {}
const Detail: React.FC<DetailProps> = (props) => {
return (
<div>
<span className="tituloJuego">DENTRO DE DETAIL</span>
</div>
);
};
interface CardProps {
id: number;
image: string;
title: string;
price: string;
onClick: (result: string) => void;
}
const Card: React.FC<CardProps> = (props) => {
return (
<div className="card">
<img className="image" src={props.image} />
<div className="card-info">
<div className="containerTitlePrice">
<span className="tituloJuego">{props.title}</span>
<br></br>
<span className="-USD">{props.price}</span>
</div>
<div className="containerButton">
<button className="Rectangle" onClick={(event) => props.onClick("detail")}>
<span className="Install-Game">BUY NOW</span>
</button>
</div>
</div>
</div>
);
};
interface GalleryProps {
onClick: (result: string) => void;
}
const Gallery: React.FC<GalleryProps> = (props) => {
return (
// Se recorren los datos de json con map y se imprime cada card
<div className="container">
{data.map((games, key) => {
return (
<Card
id={games.id}
image={games.image}
title={games.title}
price={games.price}
onClick={(event) => props.onClick("detail")}
/>
);
})}
</div>
);
};
const Homepage: React.FC = () => {
// Por defecto el estado muestra homepage
const [currentView, setCurrentView] = React.useState<string>("homepage");
const handleClick = () => setCurrentView("detail");
const [currentClickedGame, setCurrentClickedGame] = React.useState<number>(0);
return (
<div className="Simplified-homepage">
{currentView === "homepage" ? (
<div className="store-gallery">
<Gallery onClick={handleClick} />
</div>
) : (
<div className="">
<Detail />
</div>
)}
</div>
);
};
// ========================================
ReactDOM.render(<Homepage />, document.getElementById("root"));
Here is the json file. The data from this file is the data that im showing in the Cards. The "ID" is the number that I want to pass from Card to Homepage:
JSON file
You can pass the state currentClickedGame in both the child components for handling the ID.
You can't pass props on components in the same level or from child to parent as react follows unidirectional data flow .Also, instead of passing the handleClick from Homepage you can manage it inside Card component itself and set setCurrentView from there.
I've drilled props to 2 levels, consider using context in a more complex case
In HomePage:
return (
<div className="Simplified-homepage">
{currentView === "homepage" ? (
<div className="store-gallery">
<Gallery setCurrentClickedGame ={setCurrentClickedGame}/>
</div>
) : (
<div className="">
<Detail currentClickedGame={currentClickedGame}/>
</div>
)}
</div>
);
And in Gallery component:
return (
<div key={games.id}>
<Card
id={games.id}
image={games.image}
title={games.title}
price={games.price}
setCurrentView={props.setCurrentView}
setCurrentClickedGame={props.setCurrentClickedGame}
/>
</div>
);
View demo on Codesandbox

How to change a style of an HTML element in React?

I have two React components
class App extends React.Component {
render() {
return (
<div id="appWrapper">
<ConfigureWindow />
<button id="configureClocksButton">Configure clocks</button>
<section id="clocksHere"></section>
</div>
);
}
}
const ConfigureWindow = () => (
<div id="configureWindowWrapper">
<div id="configureWindow">
<section id="addCitySection">TODO: adding a city</section>
<div id="verticalLine"></div>
<section id="listOfCities">
<header>
<h1>Available cities</h1>
<div id="closeConfigureWindowWrapper">
<img src="..\src\images\exit.png" id="closeConfigureWindow" alt="" />
</div>
</header>
<section id="availableCities"></section>
</section>
</div>
</div>
);
I want "ConfigureWindow" to be shown when "configureClocksButton". I tried to execute it with props, state and a function but got errors. It also would be nice if you explain me how to create new React components with React functions?
You probably want to use the React.JS event onClick (https://reactjs.org/docs/handling-events.html), and a state to store the action. To create a function component, you just have to return the JSX you want to render, and use hooks (https://reactjs.org/docs/hooks-intro.html) and then do a conditional rendering (https://reactjs.org/docs/conditional-rendering.html):
const App = () => {
const [toggleConfiguration, setToggleConfiguration] = useState(false)
return (
<div id="appWrapper">
{toggleConfiguration && <ConfigureWindow />}
<button onClick{() => setToggleConfiguration(true)} id="configureClocksButton">Configure clocks</button>
<section id="clocksHere"></section>
</div>
);
}
It's a bit difficult to understand your post, but I gather you want to click the button with id="configureClocksButton" and conditionally render the ConfigureWindow component.
You can accomplish this with some boolean state, a click handler to toggle the state, and some conditional rendering.
class App extends React.Component {
this.state = {
showConfigureWindow: false,
}
toggleShowConfigureWindow = () => this.setState(prevState => ({
showConfigureWindow: !prevState.showConfigureWindow,
}))
render() {
return (
<div id="appWrapper">
{showConfigureWindow && <ConfigureWindow />}
<button
id="configureClocksButton"
onClick={this.toggleShowConfigureWindow}
>
Configure clocks
</button>
<section id="clocksHere"></section>
</div>
);
}
}
A function component equivalent:
const App = () => {
const [showConfigureWindow, setShowConfigureWindow] = React.useState(false);
const toggleShowConfigureWindow = () => setShowConfigureWindow(show => !show);
return (
<div id="appWrapper">
{showConfigureWindow && <ConfigureWindow />}
<button
id="configureClocksButton"
onClick={toggleShowConfigureWindow}
>
Configure clocks
</button>
<section id="clocksHere"></section>
</div>
);
}

How to join an array with folder images in React

I am trying to render a child component with images from local folder, but I don't know how to do it.
So I have a const array with details about several projects. Each of the project has its own folder with images. The project name is equal folder name with images
Parent component
import { myProjects } from '../lib/Projects'; //its array with projects
export default class Parent extends Component {
render() {
// function for images
function importAll(r) {
return r.keys().map(r);
}
const projectA = importAll(require.context('../../assets/images/projectA', false, /\.(png|jpe?g|svg)$/));
const projects = myProjects.map((project, i) =>
<Child id={i} key={i} project={project} />)
return (
<div className="main-container">
{projects}
</div>
)
}
}
Child component
export default class Child extends Component {
render() {
const { project } = this.props;
return (
<div className="item">
<div className="desc">
<div className="item-name">
<p>{project.name}</p>
</div>
<div className="item-description">
<p>{project.description}</p>
</div>
<div className="item-tools">
<p>{project.tools}</p>
</div>
</div>
// this part works well
// How to make below part work?
<div className="image-block">
<div className="item-image-first">
<img src={project.name[0]} alt=""/>
</div>
<div className="item-images">
{project.name ? project.name.map((image, index) => {
return (
<div className="image-block-small" key={index}>
<ModalImage
small={image}
large={image}
alt=""
hideDownload={true}
hideZoom={true}
className="modal-image"
/>
</div>
)
})
: null }
</div>
</div>
</div>
)
}
}
Maybe there is a way to add an extra array here?
const projects = myProjects.map((project, i) =>
<Child id={i} key={i} project={project} />)
Any suggestion?

Accesing object using props in ReactJs

I'm trying to access object keys using props as an index but it's not working. Error: Objects are not valid as a React child (found: object with keys {id, name, img_url, location}). If you meant to render a collection of children, use an array instead.
I am new to React so I appreciate any help.
My code:
class ExpandCard extends React.Component {
render() {
const props = this.props;
const profiles = props.profiles;
return(
<>
<div className="">
{profiles[props.active]}
</div>
</>
);
}
}
class App extends React.Component {
state = {
profiles: testData,
active: null,
}
getActive = (dataFromCard) => {
console.log('the magic number is', dataFromCard);
this.setState({active: dataFromCard});
}
render() {
return (
<div>
<div className="wrapper">
<header>
<div className="logo">LOGO</div>
</header>
<Form />
<div className="cards">
<div className="card-list">
{this.state.profiles.map(profile => <Card key={profile.id} {...profile} activeCard={this.getActive} />)}
</div>
<div className="expand-card">
<ExpandCard active={this.state.active} profiles={this.state.profiles} />
</div>
</div>
</div>
</div>
);
}
}
It looks like {profiles[props.active]} returns an object that looks like this:
{ id, name, img_url, location }
You can't return an object from a React component, maybe you meant to return {profiles[props.active].name}?

Hide sub-component in components generated with map() in React

I'm working on a todo app.
When a task is clicked
setState({displayTaskMenu: true})
is called,
but I also need to hide the taskMenu on all the other Task components where displayTaskMenu is true.
What is the correct way of doing this with React?
This is the render method of Task component:
render() {
let { task } = this.props;
return (
<div onClick={this.toggleMenu}>
<div>
{task.label}
{this.state.displayTaskMenu && (<TaskMenu />)}
</div>
</div>
);
}
Tasks are sorted by days, and the render of Day component is:
render() {
return (
<div style={this.state.dayStyle}>
<span style={this.state.dateStyle}>{this.props.date}</span>
<h1>{this.props.day}</h1>
{this.props.tasks &&
this.props.tasks.map(
(task, i) => <Task key={i} task={task}/>
)}
</div>
);
}
Since only one Task component should open their TaskMenu, the Day component needs to keep track of which Task it is by using state. So, the Day component should pass in a custom onClick function to each Task an update its state.
Day component:
render() {
return (
<div style={this.state.dayStyle}>
<span style={this.state.dateStyle}>{this.props.date}</span>
<h1>{this.props.day}</h1>
{this.props.tasks &&
this.props.tasks.map(
(task, i) => <Task
key={i}
task={task}
displayMenu={this.state.displaying == i}
onClick={ () => this.setState({displaying: i}) }/>
)}
</div>
);
}
Task component:
render() {
let { task } = this.props;
return (
<div onClick={this.props.onClick}>
<div>
{task.label}
{this.props.displayMenu && (<TaskMenu />)}
</div>
</div>
);
}
Using this approach, you don't need the toggleMenu function in the Task component anymore.

Resources