Component in Component, React redux - reactjs

Is it possible to call component in component (Like inception)
Example
Content.jsx
class Content extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.dispatch(fetchNav(this.props.match.params.tab));
}
componentDidUpdate(prevProps) {
if(this.props.match.params.tab != prevProps.match.params.tab) {
this.props.dispatch(fetchNav(this.props.match.params.tab));
}
}
render() {
const {nav, match} = this.props;
let redirect = null;
return (
<div>
<ul>
{nav.some((item, key) => {
if (this.props.location.pathname != (match.url + item.path)) {
redirect = <Redirect to={(match.url + item.path)} />;
}
return true;
})}
{redirect}
{nav.map((item, key) => {
return <li key={key}>
<Link to={match.url + item.path}>{item.name}</Link>
</li>;
})}
<Switch>
{nav.map((item, key) => {
return <Route key={key} path={`${match.url}/list/:tab`} component={Content} />;
})}
</Switch>
</ul>
</div>
);
}
}
const mapStateToProps = (state, props) => {
const {fetchNav} = state;
const {
lastUpdated,
isFetching,
nav: nav
} = fetchNav[props.match.params.tab] || {
isFetching: true,
nav: []
};
return {
nav,
isFetching,
lastUpdated
}
};
export default connect(mapStateToProps)(withStyles(appStyle)(Content));
Actually when i do this, if my route match and call same "Content" component, it says : "this.props.dispatch is not a function"
Do you think i need to create a ContentContainer that manage connect() and pass a method via props to manage change ?
Many thanks for you answers
Regards,
Thomas.

You are clearly not mapping dispatch to your props in your connect.
See the docs for redux' connect method:
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
You can map the dispatch function to your props like like this:
const mapDispatchToProps = (dispatch) => ({ dispatch });
connect(mapStateToProps, mapDispatchToProps)...
Do you think i need to create a ContentContainer that manage connect() and pass a method via props to manage change ?
You don't need a container. The separation of code between a container and a (view-) component is just a pattern and not required.
As a sidenote: I would recommend to use compose to combine your HOCs, see this or this.

Related

How can I update a single React Component from multiple different components?

I'm learning React and still trying to figure out how to plan out and implement some things. I have an app that makes three different API calls and thus three different return values. I'd like to have a global status component that tells me if all three loaded or not. Here's my psuedo code since I haven't found the proper way to do this yet, but this is effectively my train of thought at the moment. I have the main app:
const App = () => {
return (
<div>
<GenericAPICallerA />
<GenericAPICallerB />
<GenericAPICallerC />
<div>
<APIStatus/>
</div>
</div>
);
}
This is the APIStatus which just returns if all A, B, and C API calls have loaded or not:
class APIStatus extends React.Component{
constructor(props){
super(props);
this.state = {
aLoaded: false,
bLoaded: false,
cLoaded: false,
};
}
render(){
if (this.state.aLoaded && this.state.bLoaded && this.state.cLoaded){
return <div>Everything has loaded!</div>
}
else{
return <div>Nothing has loaded!</div>
}
}
}
And finally one of the APICaller components. The others are essentially the same:
class GenericAPICallerA extends React.Component{
constructor(props){
super(props);
this.state = {
error: null,
isLoaded: false,
};
}
componentDidMount(){
fetch("example.com/api",{
method: 'GET',
})
.then(res => res.json())
.then(
(result) =>{
this.setState({
isLoaded: true,
});
},
(error) =>{
this.setState({
isLoaded: false,
error
});
}
)
}
render(){
const { error, isLoaded, profile } = this.state;
if (error){
return <div>Errored!</div>
} else if (!isLoaded){
// APIStatus.state.aLoaded=false
} else {
// APIStatus.state.aLoaded=true
return(
<div>Generic Caller A is done!</div>
);
}
}
}
The comments in the render section are what I don't know how to do. I feel like I should pass in the APIStatus as a prop to the GenericAPICaller but I'm still unsure how I would update that property from inside the GenericAPICaller.
Thanks!
You can create a function in parent component and pass it to the child will be triggered and pass a state variable to the child where it will be used
For example:
import React from 'react'
import ComponentA from './ComponentA'
import ComponentB from './ComponentB'
class App extends React.Component
{
constructor()
{
super()
this.state = { my_state_variable:`some value` }
}
my_function()
{
this.setState({ my_state_variable:`new value` })
}
render = () => <>
<ComponentA my_function={my_function} />
<ComponentB my_state_variable={my_state_variable} />
</>
}
export default App
ComponentA
import React from 'react'
const ComponentA = ({ my_function }) => <>
<button onClick={() => my_function() }>Click Me </button>
</>
export default ComponentA
ComponentB
import React from 'react'
const ComponentB = ({ my_state_variable }) => <>
<p>{my_state_variable}</p>
{my_state_variable === `some value` && <p>if some value this will render </p>}
{my_state_variable === `new value` && <p>if new value this will render </p>}
</>
export default ComponentA
You can use context to accomplish this. By using context, you are able to access the value you provide to it as long as the component you attempt to access it through is a child of a provider.
The example below illustrates how you can access a shared state between multiple components at different levels without having to pass props down.
const {
useState,
useEffect,
createContext,
useContext,
Fragment
} = React;
const MainContext = createContext({});
function Parent(props) {
const [state, setState] = useState({child1:false,child2:false,child3:false});
return <MainContext.Provider value={{state,setState}}>
<Child1/> {state.child1? 'loaded':'not loaded'}
<br/>
<Child2/>
</MainContext.Provider>;
}
function Child1(){
const {state, setState} = useContext(MainContext);
return <button onClick={()=>setState({...state, child1:!state.child1})}>Load Child 1</button>;
}
function Child2(){
const {state, setState} = useContext(MainContext);
return <Fragment>
<button onClick={()=>setState({...state, child2:!state.child2})}>Load Child 2</button> {state.child2? 'loaded':'not loaded'}
<br/>
<Child3/> {state.child3? 'loaded':'not loaded'}
</Fragment>;
}
function Child3(){
const {state, setState} = useContext(MainContext);
return <button onClick={()=>setState({...state, child3:!state.child3})}>Load Child 3</button>;
}
const el = document.querySelector("#root");
ReactDOM.render(<Parent/>, el);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Understanding react js constructor in children component

I would like to understand the behavior of react component constructor. Let suppose I have three components - PageComponent, ListComponent, ItemComponent. My pseudo-code structure is:
PageComponent (get data from redux, fetch data)
ListComponent (obtains data as props, in loop (map) renders list of ItemComponents)
ItemComponent (obtains item data as props, renders item, manipulate data)
Logic:
- when data in ItemComponent changes, changes are stored in REDUX and this change caused list re-rendering.
Use-case 1:
- PageComponent renders ListComponent and ListComponent renders list of ItemComponets
- when REDUX listItem data chages, PageComponent is updated, ListComponent is updated and ItemComponent CONSTRUCTOR is called (its local state is reset)
Use-case 2:
- PageComponent renders only LIST (using map loop) of ItemComponents.
- when REDUX listItem data chages, PageComponent is updated ItemComponent CONSTRUCTOR is NOT called (component is "only" updated) (and its local state is NOT reset)
Why there is a different behavior in these examples?
Source code:
PageComponent:
import React from 'react'
...
class UsersPage extends React.Component {
constructor(props) {
super(props)
props.actions.getUsers();
}
render() {
const {users} = this.props
return (
<Main>
{/* // NO ITEM CONSTRUCTOR IS CALLED
users.data.items.map((item, index) => {
return <ListItemComponent
data={item}
itemMethods={{
getItem: (data) => this.props.actions.getUser(data),
onEdit: (data) => this.props.actions.updateUser(data),
onDelete: (data) => this.props.actions.deleteUser(data),
validation: (data) => validateInput(this.props.strings, data)
}}
key={index}
/>
})*/
}
{ // ITEM CONSTRUCTOR IS CALLED
<ListComponent
loading={users.isFetching}
data={users.data}
methods={{
getItem: (data) => this.props.actions.getUser(data),
onEdit: (data) => this.props.actions.updateUser(data),
onDelete: (data) => this.props.actions.deleteUser(data),
validation: (data) => validateInput(this.props.strings, data)
}}
/>}
</Main>
);
}
}
UsersPage.propTypes = {
users: PropTypes.object.isRequired,
strings: PropTypes.object.isRequired,
}
function mapStateToProps(state) {
return {
users: state.users,
strings: state.strings.data || {},
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
getUsers,
getUser,
addUser,
updateUser,
deleteUser,
}, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(withAlert(UsersPage));
ListComponent:
import React from 'react'
...
class ListComponent extends React.Component {
getList() {
return <div className="list-outer">
<Row>
{
items.map((item, index) => {
return <ListItemComponent
data={item}
itemMethods={methods}
key={index}
/>
})
}
</Row>
</div>
}
render() {
const {loading} = this.props
return (
<div className="list-wrapper">
{
loading ? <Spinner visible={true}/>
:
this.getList()
}
</div>
)
}
}
ListComponent.propTypes = {
loading: PropTypes.bool.isRequired,
data: PropTypes.object.isRequired,
methods: PropTypes.object.isRequired,
}
export default ListComponent
ListItemComponent:
import React from 'react'
...
class ListItemComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
editMode: false,
}
}
toggleEditMode(){
const editMode = this.state.editMode
this.setState({editMode: !editMode})
}
onEdit(id) {
const itemMethods = this.props.itemMethods
this.toggleEditMode()
itemMethods.getItem({id: id})
}
onDelete(item) {
//...
}
getFields(rowData, index) {
return <div key={index}>
{
rowData.map((itm, idx) => {
return <div key={idx}>{itm.label}: {itm.value}</div>
})
}
</div>
}
render() {
const editMode = this.state.editMode
const {data, itemMethods, strings} = this.props
return (
editMode ?
<Form
id={data.id}
onSubmit={(data) => itemMethods.onEdit(data)}
validation={(data) => itemMethods.validation(data)}
onCloseForm={() => this.toggleEditMode()}
/>
:
<Col xs={12}>
<div>
<div
{this.getFields(data)}
</div>
<div className="controls">
<button
className="btn btn-theme inverse danger"
onClick={() => this.onDelete(data)}
>{strings.delete}</button>
<button
onClick={() => this.onEdit(data.id)}
className="btn btn-theme" type="button"
>
{strings.edit}
</button>
</div>
</div>
</Col>
)
}
}
ListItemComponent .propTypes = {
strings: PropTypes.object.isRequired,
data: PropTypes.object.isRequired,
itemMethods: PropTypes.object.isRequired,
}
function mapStateToProps(state) {
return {
strings: state.strings.data || {}
};
}
export default connect(
mapStateToProps,
null,
)(ListItemComponent )
Ensure each ItemComponent has a key prop set. When React renders your list of items, it needs to know how to identify each element and React leaves it up to you to do this. If you omit the key prop, React will destroy and re-create your list upon each re-render, which means calling the component constructor.
If you provide the exact code you're using, we can better point out where your issue is coming from.
You can read more about lists and keys here.
SOLVED
It was cause by ListComponent and the loading prop that was placed as condion in render function. When item was edited, prop loading was set to true, spinner became visible AND it was the only element in ListComponent and therefore the list items were unmounted

React Transfer internal prop of Parent to Child

<Parent><!-- has an internal prop 'json', is set from a fetch request -->
<div>
<div>
<Child /><!-- how can I send 'json here? -->
Do I have to use React context? I find it very confusing. After writing a component like that and looking back at the code I am just confused https://reactjs.org/docs/context.html
https://codesandbox.io/embed/bold-bardeen-4n66r?fontsize=14
Have a look at the code, context is not that necessary you can elevate data to parent component, update it and share it from there only.
For what I know, there are 3 or 4 alternatives:
1) Using context as you said, so declaring a provider and then consuming it with useContext() at the component where you need it. It may reduce reusability of components,
2) Lift state & props, among descendant components
const App = (props: any) => {
// Do some stuff here
return <Parent myProp={props.myProp}></Parent>;
};
const Parent = ({ myProp }: any) => {
return (
<div>
<Child myProp={myProp}></Child>
</div>
);
};
const Child = ({ myProp }: any) => {
return (
<div>
<GrandChild myProp={myProp}></GrandChild>{" "}
</div>
);
};
const GrandChild = ({ myProp }: any) => {
return <div>The child using myProp</div>;
};
export default App;
3) Using children:
const App = (props: any) => {
// Do some stuff here
return (
<Parent>
<GrandChild myProp={props.myProp}></GrandChild>
</Parent>
);
};
const Parent = (props: any) => {
return (
<div>
<Child>{props.children}</Child>
</div>
);
};
const Child = (props: any) => {
return <div>{props.children}</div>;
};
const GrandChild = ({ myProp }: any) => {
return <div>The child using myProp</div>;
};
4) Pass the GrandChild itself as a prop in the Parent, lifting it down to the proper Child and render it there. It's actually a mix of the previous 2 alternatives.
This is a simple example where you send the response through props to child. I used some sample (api) to demonstrate it.
------------------------Parent Component------------------------
import React, { Component } from "react";
import Axios from "axios";
import Child from "./Child";
let clicked = false;
class App extends Component {
state = {
parentResponse: ""
};
fetchAPI = () => {
Axios.get("https://jsonplaceholder.typicode.com/todos/1")
.then(res => {
if (res && res.data) {
console.log("Res data: ", res.data);
this.setState({ parentResponse: res.data });
}
})
.catch(err => {
console.log("Failed to fetch response.");
});
};
componentDidMount() {
this.fetchAPI();
}
render() {
return (
<div>
<Child parentResponse={this.state.parentResponse} />
</div>
);
}
}
export default App;
------------------------Child Component------------------------
import React, { Component } from "react";
class Child extends Component {
state = {};
render() {
return (
<div>
{this.props.parentResponse !== "" ? (
<div>{this.props.parentResponse.title}</div>
) : (
<div />
)}
</div>
);
}
}
export default Child;

Pass state value to component

I am really new in React.js. I wanna pass a state (that i set from api data before) to a component so value of selectable list can dynamically fill from my api data. Here is my code for fetching data :
getListSiswa(){
fetch('http://localhost/assessment-app/adminpg/api/v1/Siswa/')
.then(posts => {
return posts.json();
}).then(data => {
let item = data.posts.map((itm) => {
return(
<div key={itm.siswa_id}>
<ListItem
value={itm.siswa_id}
primaryText={itm.nama}
/>
</div>
)
});
this.setState({item: item});
});
}
From that code, i set a state called item. And i want to pass this state to a component. Here is my code :
const ListSiswa = () => (
<SelectableList>
<Subheader>Daftar Siswa</Subheader>
{this.state.item}
</SelectableList>
);
But i get an error that say
TypeError: Cannot read property 'item' of undefined
I am sorry for my bad explanation. But if you get my point, i am really looking forward for your solution.
Here is my full code for additional info :
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {List, ListItem, makeSelectable} from 'material-ui/List';
import Subheader from 'material-ui/Subheader';
let SelectableList = makeSelectable(List);
function wrapState(ComposedComponent) {
return class SelectableList extends Component {
static propTypes = {
children: PropTypes.node.isRequired,
};
getListSiswa(){
fetch('http://localhost/assessment-app/adminpg/api/v1/Siswa/')
.then(posts => {
return posts.json();
}).then(data => {
let item = data.posts.map((itm) => {
return(
<div key={itm.siswa_id}>
<ListItem
value={itm.siswa_id}
primaryText={itm.nama}
/>
</div>
)
});
this.setState({item: item});
});
}
componentWillMount() {
this.setState({
selectedIndex: this.props.defaultValue,
});
this.getListSiswa();
}
handleRequestChange = (event, index) => {
this.setState({
selectedIndex: index,
});
};
render() {
console.log(this.state.item);
return (
<ComposedComponent
value={this.state.selectedIndex}
onChange={this.handleRequestChange}
>
{this.props.children}
</ComposedComponent>
);
}
};
}
SelectableList = wrapState(SelectableList);
const ListSiswa = () => (
<SelectableList>
<Subheader>Daftar Siswa</Subheader>
{this.state.item}
</SelectableList>
);
export default ListSiswa;
One way to do it is by having the state defined in the parent component instead and pass it down to the child via props:
let SelectableList = makeSelectable(List);
function wrapState(ComposedComponent) {
return class SelectableList extends Component {
static propTypes = {
children: PropTypes.node.isRequired,
};
componentWillMount() {
this.setState({
selectedIndex: this.props.defaultValue,
});
this.props.fetchItem();
}
handleRequestChange = (event, index) => {
this.setState({
selectedIndex: index,
});
};
render() {
console.log(this.state.item);
return (
<ComposedComponent
value={this.state.selectedIndex}
onChange={this.handleRequestChange}
>
{this.props.children}
{this.props.item}
</ComposedComponent>
);
}
};
}
SelectableList = wrapState(SelectableList);
class ListSiswa extends Component {
state = {
item: {}
}
getListSiswa(){
fetch('http://localhost/assessment-app/adminpg/api/v1/Siswa/')
.then(posts => {
return posts.json();
}).then(data => {
let item = data.posts.map((itm) => {
return(
<div key={itm.siswa_id}>
<ListItem
value={itm.siswa_id}
primaryText={itm.nama}
/>
</div>
)
});
this.setState({item: item});
});
}
render() {
return (
<SelectableList item={this.state.item} fetchItem={this.getListSiswa}>
<Subheader>Daftar Siswa</Subheader>
</SelectableList>
);
}
}
export default ListSiswa;
Notice that in wrapState now I'm accessing the state using this.props.item and this.props.fetchItem. This practice is also known as prop drilling in React and it will be an issue once your app scales and multiple nested components. For scaling up you might want to consider using Redux or the Context API. Hope that helps!
The error is in this component.
const ListSiswa = () => (
<SelectableList>
<Subheader>Daftar Siswa</Subheader>
{this.state.item}
</SelectableList>
);
This component is referred as Stateless Functional Components (Read)
It is simply a pure function which receives some data and returns the jsx.
you do not have the access this here.

Reusable React component with same actions & reducers

I want to reuse a react component and share common actions & reducers. My app dashboard has 3 Lists, where each List is fetched with different query param.
All 3 List components have the same props because all 3 of them are being re-rendered once I receive props from reducer.
Is there an dynamic way to display Lists based on query parameter? What I was thinking is to call different reducer in the action file based on the query param. Is there a better way?
Dashboard.js
const Dashboard = () => {
return(
<div>
<List query={QUERY1} />
<List query={QUERY2} />
<List query={QUERY3} />
</div>
)
}
List.js
class List extends Component {
constructor(props) {
super(props);
this.state = {
items: []
};
}
componentWillMount() {
const { query } = this.props;
this.props.onLoad(query);
}
componentWillReceiveProps() {
const { items } = this.props;
this.setState({ items });
}
render() {
return (
<div>
{
this.state.items.map((item, index) =>
<Item data={item} key={index}/>
)
}
</div>
)
}
}
function mapStateToProps(state) {
const { items } = state.item;
return {
items
}
}
function mapDispatchToProps(dispatch) {
return {
onLoad: bindActionCreators(actions.load, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(List);
action.js
export function load(query) {
return function (dispatch) {
fetch(`//api.example.com/list?type=${query}&limit=10`)
.then((response) => response.json())
.then((data) => {
dispatch(setItems(data));
});
};
}
reducer.js
export default function(state = [], action) {
switch (action.type) {
case actionTypes.ITEMS_SET:
return setItems(state, action);
}
return state;
}
function setItems(state, action) {
const { items } = action;
return { ...state, items };
}
Note I am a contributor on redux-subpace
redux-subspace came around to solve this problem of having the same component displayed on the page, without crossing over the store values.
It has a feature called namespacing that will allow you to isolate your load actions and components from each other.
const Dashboard = () => {
return(
<div>
<SubspaceProvider mapState={state => state.list1}, namespace='list1'>
<List query={QUERY1} />
</SubspaceProvider>
<SubspaceProvider mapState={state => state.list2}, namespace='list'>
<List query={QUERY2} />
</SubspaceProvider>
<SubspaceProvider mapState={state => state.list3}, namespace='list3'>
<List query={QUERY3} />
</SubspaceProvider>
</div>
)
}
You'll also need to namespace your reducers, you can see how to do that here.

Resources