React Hooks: Independent state in dynamic children - reactjs

I am generating my state in the parent component. latestFeed generates a series of posts from my backend:
import React, { useState, useEffect } from "react";
import { getLatestFeed } from "../services/axios";
import Childfrom "./Child";
const Parent= () => {
const [latestFeed, setLatestFeed] = useState("loading");
const [showComment, setShowComment] = useState(false);
useEffect(async () => {
const newLatestFeed = await getLatestFeed(page);
setLatestFeed(newLatestFeed);
}, []);
return (
<div className="dashboardWrapper">
<Child posts={latestFeed} showComment={showComment} handleComment={handleComment} />
</div>
);
};
export default Parent;
then latestFeed gets generated into a series of components that all need to hold their own state.
import React, { useState } from "react";
const RenderText = (post, showComment, handleComment) => {
return (
<div key={post._id} className="postWrapper">
<p>{post.title}</p>
<p>{post.body}</p>
<Comments id={post._id} showComment={showComment} handleComment={() => handleComment(post)} />
</div>
);
};
const Child= ({ posts, showComment, handleComment }) => {
return (
<div>
{posts.map((post) => {
return RenderText(post, showComment, handleComment);
})}
</div>
);
};
export default Child;
In its current form, the state of RenderText's is all set at the same time. I need each child of Child to hold its own state.
Thank you!

Instead of using RenderText as a function, call it as a component:
{posts.map((post) => (
<RenderText key={post.id} post={post} showComment={showComment} />
))}
This is because when used as a component, it will have it's own lifecycle and state. If used as a function call, React does not instantiate it the same way - no lifecycle, no state, no hooks, etc.

Related

Uncaught Error: Rendered more hooks than during the previous render when trying to have a nested loop

I know same question probably asked multiple times. But I couldn't find the answer I'm looking for.
This is the code for Task:
import Navbar from './Navbar';
import "./Idea.css";
import GetData from '../restApiMethods/GetData';
import React from "react";
function Task() {
const ids = GetData("ideas/id");
return (
<div>
<Navbar />
<div className="idea-design">
<div className="container">
{
ids.map((id,index) => {
return (
<div key={index}>
{
GetData(`ideas/${id}`).map((task,index) => {
return(
<div key={index} className="row border border-secondary">
<div className="col">
<div>
<p>{task.taskDescription}</p>
</div>
</div>
</div>
)
})
}
</div>
)
})
}
</div>
</div>
</div>
)
}
export default Task
Getdata dunction:
import axios from "axios";
import {useState, useEffect} from "react";
function GetData(data) {
const [datas, setDatas] = useState([]);
useEffect(() =>{
const fetchData = () => {
axios.get(`http://localhost:8080/api/${data}`).then(res =>{
console.log(res);
setDatas(res.data);
});
};
fetchData();
}, []);
return datas;
}
export default GetData
If someone can give me some idea why I'm getting this error: Rendered more hooks than during the previous render, would be really helpful.
GetData actually is a custom hook because it's a function that calls hooks. Therefore subject to the rules of hooks.
It should be called useGetData -- I'll refer to it as that for this answer. You can't call it in a loop, as when the ids array changes length, the number of calls to useGetData will change in the parent component Task. This isn't allowed in React because hooks are supposed to be in a predictable order and never change -- it's a declarative model.
To fix this, break out a new component called Task (rename your current one to Tasks or whatever makes sense for you) and call it once in there. This doesn't break the rules of hooks as it is only within a component that the number of calls can't change between renders.
Tasks
import Navbar from "./Navbar";
import "./Idea.css";
import useGetData from "../restApiMethods/useGetData";
import React from "react";
import Task from "./Task";
function Tasks() {
const ids = useGetData("ideas/id");
return (
<div>
<Navbar />
<div className="idea-design">
<div className="container">
{ids.map((id, index) => {
return <Task id={id} key={id} />;
})}
</div>
</div>
</div>
);
}
export default Tasks;
Task
export default function Task({ id }) {
const data = useGetData(`ideas/${id}`);
return (
<div>
{data.map((task, index) => {
return (
<div key={index} className="row border border-secondary">
<div className="col">
<div>
<p>{task.taskDescription}</p>
</div>
</div>
</div>
);
})}
</div>
);
}
import axios from "axios";
import { useState, useEffect } from "react";
function useGetData(data) {
const [datas, setDatas] = useState([]);
useEffect(() => {
const fetchData = () => {
axios.get(`http://localhost:8080/api/${data}`).then((res) => {
console.log(res);
setDatas(res.data);
});
};
fetchData();
}, []);
return data;
}
export default useGetData;

logging unmount for JSX element instead of components

how we can observe if a JSX element mounted or not. for example I have a simple component with useEffect on. it inside of my App.js I can mount and unmount my component and the useEffect inside of that component will log if it is mounted or unmounted.
but I wonder if there is way to that with JSX elements. for example , can we implement that for an h2 tag inside of an App.js without creating component ?
App.js
import React, { useState } from "react";
import "./App.css";
import Mycomponent from "./Mycomponent";
const App = () => {
const [mount, setMount] = useState(true);
return (
<div>
<b>Mounting and Unmounting</b>
<button
onClick={() => {
setMount(!mount);
}}
>
{mount ? "click to unmount" : "click to mount"}
</button>
{mount && <Mycomponent />}
</div>
);
};
export default App;
Mycomponent.js :
import React, { useEffect } from "react";
const Mycomponent = () => {
useEffect(() => {
console.log("mounted");
return () => {
console.log("unmounted");
};
}, []);
return (
<div>
<h1>component mounted</h1>
</div>
);
};
export default Mycomponent;
I think you can use callback refs for that:
export default function App() {
const [counter, setCounter] = React.useState(0);
const measuredRef = (node) => {
if (node == null) {
console.log('I was removed');
} else {
console.log('I was mounted');
}
};
return (
<div
onClick={() => {
setCounter(counter + 1);
}}
>
{counter % 2 == 0 && <h1 ref={measuredRef}>Hello, world</h1>}
<p>Start editing to see some magic happen :)</p>
</div>
);
}
There is a somewhat related example in the docs about that:
In this example, the callback ref will be called only when the
component mounts and unmounts, since the rendered <h1> component stays
present throughout any rerenders.

Passing down Props to "Single Page Views" through components

Hey still new to React but I'm grinding my way through it slowly by building my own personal app/platform. I have a quick question of passing down props to single page views. This is my overview page that will pull in all the teams from my database as such:
import React, { useState, useEffect } from 'react';
import firebase from '../../firebase/firebase.utils'
import Button from '../../Components/GeneralComponents/Button.component'
import * as GoIcons from 'react-icons/go';
import TeamList from '../../Components/Teams/TeamList.Component'
function TeamsPage() {
const [teams, setTeams] = useState([]);
const [loading, setLoading] = useState(false);
const ref = firebase.firestore().collection("teams");
function getTeams() {
setLoading(true);
ref.onSnapshot((querySnapshot) => {
const items = [];
querySnapshot.forEach((doc) => {
items.push(doc.data());
});
setTeams(items);
setLoading(false);
console.log(items);
});
}
useEffect(() => {
getTeams();
},[])
if(loading) {
return <h1>Loading...</h1>
}
return (
<div className="content-container">
<h2>Team Page</h2>
<div className="add-section">
<div className="actions">
<Button
className="bd-btn outlined add-team"
><GoIcons.GoGear/>
Add Team
</Button>
</div>
</div>
<TeamList teams={teams} />
</div>
)
}
export default TeamsPage;
This gets passed into my TeamList Component:
import React from 'react';
import { Link } from 'react-router-dom'
import { TeamCard } from './TeamCard.Component';
const TeamList = props => {
return(
<div className='teams-overview'>
{props.teams.map(team => (
<Link to={`/teams/${team.id}`}>
<TeamCard key={team.id} team={team}/>
</Link>
))}
</div>
)
}
export default TeamList;
Which maps through and then list the Team as a card component with a link that is supposed to route to their id and pass through their data.
Now in my single page view of a team I'm struggling to gain access to that prop data:
import React from 'react'
function TeamSinglePage(team) {
return (
<div className="content-container">
<h1>Single Page View</h1>
<p>Welcome, {team.teamName}</p>
</div>
)
}
export default TeamSinglePage;

How to get ref on children using react hook?

I'm trying to get hold of ref on children component but it doesn't seem to be working. The same approach works fine with the React class component but not with hooks.
import React, { useState, useRef } from "react";
export default function TestContainer(props) {
const ref = useRef(null);
return (
<div className="test-container" onClick={() => console.log(ref) // this logs null always}>
{React.Children.map(props.children, c =>
React.cloneElement(c, {
ref: n => {
console.log(n);
ref.current = n;
},
className: "test-container"
})
)}
</div>
);
}
export function Test(props) {
return <div className="test" {...props}>
{props.children}
</div>
}
Your component is okay. It is probably because the are no children rendered to that component. I reproduced it with using TestContainer in App and put <h2>Ref</h2> as a child of TestContainer:
(removed the comment of course, since it has been hiding the } )
App.js
import React from "react";
import "./styles.css";
import TestContainer from "./TestContainer";
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<TestContainer>
<h2>Ref</h2>
</TestContainer>
</div>
);
}
TestContainer.js
import React, { useState, useRef } from "react";
export default function TestContainer(props) {
const ref = useRef(null);
return (
<div className="test-container" onClick={() => console.log(ref)}>
{React.Children.map(props.children, c =>
React.cloneElement(c, {
ref: n => {
console.log(n);
ref.current = n;
},
className: "test-container"
})
)}
</div>
);
}
CodeSndbox:
HERE

Call method from child (class based component) in parent (functional component)

I have been developing a project for three months. I need to call a method from the child component (class-based component) in the parent component (functional component). I used ref for this but it didn't work.
Here is the parent component:
import React, { useState, useEffect, useRef, createRef } from "react";
import CityPicker from "../../components/public/cityPicker";
import Chip from "../../components/forms/chip";
import Sidebar from "./sidebar";
import { Router } from "../../routes";
import NextRouter, { withRouter } from "next/router";
const Search = props => {
const [filterItem, setFilterItem] = useState();
const deleteFilterItem = createRef();
const deleteChipHandler = event => {
filterItem
? deleteFilterItem.current.onDeleteSearchableFilterItem(
event,
"stateOrProvince",
"selectedStateOrProvince"
)
: "";
};
return (
<>
<div className={filterItem ? "filters-display" : ""}>
{filterItem
? filterItem.map((item, index) => {
return (
<Chip
id={item.id}
key={index + "selected city"}
label={item.value}
onDelete={e => deleteChipHandler(e)}
/>
);
})
: ""}
</div>
<Sidebar ref={deleteFilterItem} />
</>
);
};
export default withRouter(Search);
The onDeleteSearchableFilterItem method belong to the child component.
Write the function like this in your child component. data is the data you get from your state of that component or a state from reducer if you are using redux(like this.props.data):
const onDelete = () => {
this.props.onDelete(data);
}
then call it in the parent:
import { Router } from "../../routes";
import NextRouter, { withRouter } from "next/router";
const Search = props => {
const [filterItem, setFilterItem] = useState();
const deleteFilterItem = createRef();
const deleteChipHandler = (event, data) => {
filterItem
? deleteFilterItem.current.onDeleteSearchableFilterItem(
event,
data
)
: null;
};
return (
<>
<div className={filterItem ? "filters-display" : ""}>
{filterItem
? filterItem.map((item, index) => {
return (
<Chip
id={item.id}
key={index + "selected city"}
label={item.value}
onDelete={deleteChipHandler}
/>
);
})
: ""}
</div>
<Sidebar ref={deleteFilterItem} />
</>
);
};
export default withRouter(Search);

Resources