I have implemented an app which uses react-router to handle the routes in my web-app. I want to trigger the function logintoggle which is on the Header.js component from a function from the Hompage.js component. The App.js has all the routes in one file.
Can anyone explain to me how this can be achieved with small code snippet?
App.js
render() {
const { location } = this.props;
return (
<IntlProvider
locale="a"
messages="s"
>
<Fragment>
<div>
<Headers />
<Switch>
<Route exact path="/women" component={HomePage} />
</Switch>
</div>
</Fragment>
</IntlProvider>
);
}
}
export default App;
Header
class Header extends React.Component {
constructor(props) {
super(props);
}
logintoggle(tab) {
if (this.state.activeTab !== tab) {
this.setState({
activeTab: tab
});
}
}
}
Homepage.js
class CheckOut extends Component {
constructor(props) {
super(props);
}
}
When you need to have a shared state among the components React.Context API is what you need. It allows you to create a separate context provider, which will provide the state and the methods to manipulate this state to all the components you need. In the example below I have a LoginContextProvider with activeTab state variable. I provide activeTab and setActiveTab to all the components inside LoginContextProvider's children. Header changes activeTab to 1, Homepage changes to 2 and LoginContextDebug represents the actual activeTab value.
const LoginContext = React.createContext(null);
const LoginContextProvider = ({ children }) => {
const [activeTab, setActiveTab] = React.useState(0);
return (
<LoginContext.Provider value={{ setActiveTab, activeTab }}>
{children}
</LoginContext.Provider>
);
};
const Header = () => {
// Use setActiveTab here
const { setActiveTab } = React.useContext(LoginContext);
return (
<div>
<h1>I am header</h1>
<button onClick={() => setActiveTab(1)}>Set activeTab to 1</button>
</div>
);
};
const Homepage = () => {
// Use setActiveTab here
const { setActiveTab } = React.useContext(LoginContext);
return (
<div>
<h1>I am homepage</h1>
<button onClick={() => setActiveTab(2)}>Set activeTab to 2</button>
</div>
);
};
const LoginContextDebug = () => {
const { activeTab } = React.useContext(LoginContext);
return (
<pre style={{ padding: 10, background: "lightgray" }}>
activeTab={activeTab}
</pre>
);
};
const App = () => (
<LoginContextProvider value={null}>
<Header />
<Homepage />
<LoginContextDebug />
</LoginContextProvider>
);
ReactDOM.render(<App />, document.getElementById('root'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Related
I have a list of projects on the left side, and when you click on a project, the details show up on the right. I've done this where I have a reusable component of Details.js being rendered without a page refresh and updating the URL with Next.js shallow routing.
Idea:
/pages/projects/index.js
const Projects = () => {
const { user } = useUser();
const router = useRouter();
const [projects, setProjects] = useState([]);
const [projectSelectedId, setProjectSelectedId] = useState(null);
useEffect(() => {
if (!user) return null;
const fetchProjects = async () => {
// this is where I get my projects data
};
fetchProjects();
}, [user]);
const handleProject = (data) => {
const pid = data.id;
setProjectSelectedId(pid);
router.push("/projects/", `/projects/${pid}`, { shallow: true });
};
if (!user) return null;
return (
<>
{user && (
<div>
<div>
<h1>Projects</h1>
<div>
{projects &&
projects.map((project, index) => (
<div key={index}>
<button
onClick={() => handleProject(projects[index])}
>
{project.title}
</button>
</div>
))}
</div>
</div>
<div>
{projectSelectedId && (
<Details pid={projectSelectedId} />
)}
</div>
</div>
)}
</>
);
};
export default Projects;
When a project is selected, the URL gets updated when I use shallow routing:
router.push("/projects/", `/projects/${pid}`, { shallow: true });
This works fine, but when I refresh the page, I get 404. I want it to go to the selected project with the details on refresh as well.
Your approach has a few issues, and you would need to set up a proper layout for your nextjs app
Something like this:
In your _app.js:
export default function MyApp({ Component, pageProps }) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
);
}
As in your layout.js
export default function Layout({ children }) {
return (
<>
<main className={styles.main}>
<Sidebar />
{children}
</main>
</>
);
}
Sidebar for navigation:
export default function Sidebar() {
return (
<nav className={styles.nav}>
<Link href="/">
<a>Index</a>
</Link>
<Link href="/project/1">
<a>Project 1</a>
</Link>
<Link href="/project/2">
<a>Project 2</a>
</Link>
</nav>
);
}
And so on to your project pages, you will need a structure like so:
Checkout the full example here
what is the best way for me to use logic to wrap the component? I would like to have another span to wrap Child component if showSpan is true, maybe something like following, but it does not work
const Child = () => {
return <button>click me</button>;
};
const Home = (props: { showSpan: boolean }) => {
const { showSpan } = props;
return (
<div>
{showSpan && (<span> ssss)}
<Child />
{showSpan && (</span>)}
</div>
);
};
export default function App() {
return (
<div className="App">
<h1>
<Home showSpan={false} />
</h1>
</div>
);
}
You could use Fragment (empty tag) to be the alternative to span and use them as wrapper. Should work like this:
const Child = () => {
return <button>click me</button>;
};
const Home = (props: { showSpan: boolean }) => {
const { showSpan } = props;
const Wrapper = showSpan ?
({children}) => <span>ssss {children}</span> :
({children}) => <>{children}</>;
return (
<div>
<Wrapper>
<Child />
</Wrapper>
</div>
);
};
export default function App() {
return (
<div className="App">
<h1>
<Home showSpan={false} />
</h1>
</div>
);
}
export default class App extends React.Component {
constructor() {
super();
this.state = {
todos: 5
};
}
handleClick = () => {
this.setState({ todos: this.state.todos + 1 });
};
render() {
return (
<div className="App">
<h1>ToDo List</h1>
<p>Just keep giving me things to do</p>
<p>I still have {this.state.todos} things to do</p>
<AddTodo todos={this.state.todos} handleClick={this.handleClick} />
</div>
);
}
}
I am trying to update <p>I still have {this.state.todos} things to do</p> in the Parent to increase by 1 for every button click in the Child Component. What am I missing? I am not getting any errors but it is not functional.
import React from "react";
export default function AddTodo(handleClick) {
return (
<div className="AddTodo">
<button onClick={() => handleClick}>Add Another</button>
</div>
);
}
Props is the first value passed to a functional component, it's an object and you would need to destructure handleClick from it.
export default function AddTodo({ handleClick }) {
return (
<div className="AddTodo">
<button onClick={handleClick}>Add Another</button>
</div>
);
}
Also change your handle click function to this
handleClick = () => {
this.setState(({ todos }) => ({ todos: todos + 1 }));
};
a working example
const AddTodo = ({ onClick }) => (
<div className="AddTodo">
<button onClick={onClick}>Add Another</button>
</div>
);
const App = () => {
const [todos, setTodos] = React.useState(5);
const onClick = () => {
setTodos((oldTodos) => oldTodos + 1);
};
return (
<div className="App">
<h1>ToDo List</h1>
<p>Just keep giving me things to do</p>
<p>I still have {todos} things to do</p>
<AddTodo todos={todos} onClick={onClick} />
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'))
<script src="https://unpkg.com/react#17/umd/react.production.min.js" crossorigin></script>
<script src="https://unpkg.com/react-dom#17/umd/react-dom.production.min.js" crossorigin></script>
<div id="root"></div>
This might be a double of some question, but I couldn't find the answer to the specific question that I have. I have the following code:
import React, { Component } from 'react'
class FAQContent extends Component {
constructor(props) {
super(props);
this.state = {
opened: false,
};
this.toggleBox = this.toggleBox.bind(this);
}
toggleBox() {
const { opened } = this.state;
this.setState({
opened: !opened,
});
}
render() {
return (
<div>
<div className="question">
<div className="question-title" onClick={this.toggleBox}>
Title 1
</div>
{this.state.opened && (
<div class="answer">
Content 1
</div>
)}
</div>
<div className="question">
<div className="question-title" onClick={this.toggleBox}>
Title 2
</div>
{this.state.opened && (
<div class="answer">
Content 2
</div>
)}
</div>
</div>
)
}
}
export default FAQContent
This renders 2 question titles. However, when I click on any of the questions, the state change is triggered for all the questions. What is the most efficient way of showing the specific answer of the question without showing the rest of the components?
import React, { Component } from "react";
import { render } from "react-dom";
import { Link, BrowserRouter, Route } from "react-router-dom";
class App extends Component {
state = {
openedPost: "",
posts: [
{ question: "Question 1", id: 0, user: "lenny" },
{ question: "Question 2", id: 1, user: "benny" },
{ question: "Question 3", id: 2, user: "jenny" }
]
};
showPost = id => {
this.setState({ openedPost: id });
};
render() {
return (
<div>
<BrowserRouter>
<div>
<Route
path="/"
render={() => (
<Posts showPost={this.showPost} posts={this.state.posts} />
)}
/>
<Route
exact
path={`/posts/${this.state.openedPost}`}
render={() => (
<SinglePost
openedPost={this.state.openedPost}
showPost={this.showPost}
posts={this.state.posts}
/>
)}
/>
</div>
</BrowserRouter>
</div>
);
}
}
class Posts extends Component {
onClick = id => {
this.props.showPost(id);
};
render() {
const { posts, showPost } = this.props;
return (
<div>
{posts.map(item => (
<div onClick={() => this.onClick(item.id)}>
<Link to={`/posts/${item.id}`}>{item.question} </Link>{" "}
</div>
))}
</div>
);
}
}
class SinglePost extends Component {
render() {
const { posts, openedPost } = this.props;
const filtered = posts.filter(item => item.id === openedPost);
return (
<div>
{filtered.map(item => (
<div>
{" "}
QUESTION:{item.question} ID:{item.id}{" "}
</div>
))}
</div>
);
}
}
render(<App />, document.getElementById("root"));
Example
You are using a same state to control different parts. How about you make a new question component and let it to manage its own state and just use the question component in the FAQContent component.
Question component:
export default class Question extends Component {
state = { opened: false };
toggleBox = () => this.setState(state => ({ opened: !state.opened }));
render() {
return (
<div className="question">
<div className="question-title" onClick={this.toggleBox}>
{this.props.title}
</div>
{this.state.opened && (
<div class="answer">
{this.props.content}
</div>
)}
</div>
);
}
}
FAQContent Component:
const FAQContent = () => (
<div>
<Question title="title 1" content="content 1" />
<Question title="title 2" content="content 2" />
</div>
);
export default FAQContent;
I have multiple React components that will receive the same props...
...
render () {
const { someProps } = this.props
return (
<div className="someDiv">
<Component1 someProps={someProps}/>
<Component2 someProps={someProps}/>
</div>
)
}
...
The above code works fine but is there a more dynamic way of doing this? Mabye do a .map() over an array of Component names?
Array of components should work just fine.
const Component1 = (props) => <div>Component 1</div>
const Component2 = (props) => <div>Component 2</div>
const Component3 = (props) => <div>Component 3</div>
const components = [Component1, Component2, Component3]
class App extends React.Component {
render() {
const { someProps } = this.props
return (
<div>
<h3>Root component</h3>
{components.map((Component, index) =>
<Component key={index} someProps={someProps} />
)}
</div>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>