In my current project I'm using React Context to save component references so that the header component can access them to scroll to them. I managed to successfully make it work the first time with contactRef. But when I tried to add more states to the context, they just would not register.
Console logging the context in Header.js gives me;
contactRef: {current: div.contact}
dispatch: ƒ ()
findings: undefined
locationRef: undefined
[[Prototype]]: Object
I've attached the segments involved with this, but I've narrowed down the issue to be with the INITIAL_STATE in ComponentContext.js. Adding more states does not seem to work, every time only contactRef seems to be initialised.
ComponentContext.js
import { createContext, useReducer } from "react";
const INITIAL_STATE = {
contactRef: null,
locationRef: null,
findings: true,
};
export const ComponentContext = createContext(INITIAL_STATE);
const componentReducer = (state, action) => {
switch (action.type) {
case "contact":
return { contactRef: action.ref };
case "location":
return { locationRef: action.ref };
default:
return state;
}
};
export const ComponentProvider = (props) => {
const [state, dispatch] = useReducer(componentReducer, INITIAL_STATE);
return (
<ComponentContext.Provider
value={{
contactRef: state.contactRef,
locationRef: state.locationRef,
findings: state.findings,
dispatch,
}}
>
{props.children}
</ComponentContext.Provider>
);
};
Contact.js
import React, { useContext, useEffect, useRef } from "react";
import "./index.scss";
import { contactInfo } from "../../data/contactInfo";
import ContactPhoneIcon from "#mui/icons-material/ContactPhone";
import EmailIcon from "#mui/icons-material/Email";
import { ComponentContext } from "../../context/ComponentContext";
const Contact = () => {
const componentContext = useContext(ComponentContext);
const contactRef = useRef();
useEffect(() => {
componentContext.dispatch({ type: "contact", ref: contactRef });
}, []);
return (
<div className="contact" ref={contactRef}>
<div className="contact-accent"></div>
<div className="contact-body">
<div className="contact-left">
<h1 className="contact-title">Hit Me up!</h1>
<div className="contact-info">
<div className="contact-info-item">
<ContactPhoneIcon className="contact-info-icon" />
{contactInfo.phone}
</div>
<div className="contact-info-item">
<EmailIcon className="contact-info-icon" />
{contactInfo.email}
</div>
</div>
</div>
<div className="contact-right">
<p className="contact-description">
<b>I'm great with kids</b> <i>Sejarah</i> has been my passion since
high school and I'd love to show that to your kids; that history is
not just a boring compulsory subject for SPM.
</p>
</div>
</div>
</div>
);
};
export default Contact;
Header.js
import "./index.scss";
import React, { useContext } from "react";
import { ComponentContext } from "../../context/ComponentContext";
const Header = () => {
const componentContext = useContext(ComponentContext);
return (
<div className="header">
<div className="header-logo"></div>
<div className="header-sections">
<div className="header-section-item">Introduction</div>
<div className="header-section-item">About My Classes</div>
<div
className="header-section-item"
onClick={() => {
componentContext.contactRef.current.scrollIntoView({
behavior: "smooth",
});
}}
>
Contact Me
</div>
<div
className="header-section-item"
onClick={() => {
// componentContext.state.locationRef.current.scrollIntoView({
// behavior: "smooth",
// });
console.log(componentContext);
}}
>
Location
</div>
</div>
</div>
);
};
export default Header;
Related
I have a context which contains a fetch() method used to retieve list of products from a server.I made this context so that I could reuse the fetched array values every webpage I might need.But I am unable to do so as it gives me an error in console.
this is the code for context
import React, { createContext, useState, useEffect } from 'react'
export const ProductContext = createContext()
const ProductContextProvider = (props) => {
const [product, setProduct] = useState([]);
const fetchData = () => {
fetch(`http://localhost:8080/product`)
.then((response) => response.json())
.then((actualData) => {
setProduct(actualData)
console.log(product);
})
};
useEffect(() => {
fetchData();
}, [])
return (
<ProductContext.Provider
value={{ product }}>
{props.children}
</ProductContext.Provider>
)
}
export default ProductContextProvider
and this is the error I am getting in console
enter image description here
I have done this too in index.js
enter image description here
and this is one page I want to call the product[]
import React from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import { useContext } from 'react'
import ProductContext from '../context/ProductContext';
function Product() {
const { product } = useContext(ProductContext)
console.log(product);
return (
<div className="products-row ">
{
product.map((data, num) => {
return (
<div className="product" key={num}>
<div className="card">
<a target="_blank" href="/design" >
<img src={data.thumbnail} alt={data.name} style={{ width: "100%" }} ></img>
</a>
<h1>{data.name}</h1>
<p className="price">${data.price}</p>
</div>
</div>
)
})
}
</div>
);
}
export default Product;
I believe it's an import issue. You probably meant to use the following:
import { ProductContext } from '../context/ProductContext';
Right now, your ProductContext is actually ProuductContextProvider, which is the default export as per your code.
I am trying to use React Context successfully but I a have had a lot of trouble with it. I can't even successfully pass anything one level to the provider's immediate children, as a result all I am getting at this stage is "x is undefined" errors in the console. I am using a separate class for the context an a custom hook to manage my state data.
App.js (where TodoProvider component warps around its children) -
import logo from './logo.svg';
import './App.css';
import React, {createContext, useContext} from "react"
import TodoItem from './Components/Other/TodoItem';
import TodoList from './Components/List/TodoList';
import TodoAdd from './Components/Forms/TodoAdd';
import CompletedTaskList from './Components/List/CompletedTaskList';
import useTodo from './libs/useTodo';
import {TodoContext, TodoProvider} from "./Contexts/TodoContext"
function App() {
const {
todoArray, setTodoArray,
completedTaskArray,
addCompletedItem,
addTodoItem
} = useContext(TodoContext);
return (
<TodoProvider
value={
todoArray, setTodoArray,
completedTaskArray,
addCompletedItem,
addTodoItem
}
>
<div className="App">
<div className='card' id='mainCard'>
<div className='card-header' id='mainCardHeader'><h4>Todo List</h4></div>
<TodoList/>
<TodoAdd
/>
<CompletedTaskList
/>
</div>
</div>
</TodoProvider>
)
}
export default App;
TodoContext.js (My Context) -
import React, {createContext} from "react";
import useTodo from "../libs/useTodo";
const TodoContext = createContext();
const TodoProvider = ({children}) => {
const {
todoArray, setTodoArray,
completedTaskArray,
addCompletedItem,
addTodoItem
} = useTodo();
return (
<TodoContext.Provider
value={
todoArray, setTodoArray,
completedTaskArray,
addCompletedItem,
addTodoItem
}
>
{children}
</TodoContext.Provider>
)
}
export {TodoContext, TodoProvider}
useTodo.js (My custom hook to manage state)
import React, {useState} from "react"
const useTodo = () => {
const [todoArray, setTodoArray] = useState([{id: 1,todoTitle: "Code", todoDescription: "Practice React"},{id: 2,todoTitle: "Clean", todoDescription: "Wash dishes, wipe surfaces"}]);
const [completedTaskArray, setCompletedTaskArray] = useState(["Wake up", "Make Bed"]);
const [currentId, setCurrentId] = useState(3);
const addTodoItem = (todoTitleInputItem, todoDescriptionInputItem) => {
let todoTitle = todoTitleInputItem;
let todoDescription = todoDescriptionInputItem;
let id = currentId;
setCurrentId(currentId+1)
setTodoArray(todoArray => [...todoArray, {id,todoTitle, todoDescription}]);
}
const addCompletedItem = ({todoTitle}) => {
setCompletedTaskArray(completedTaskArray => [...completedTaskArray, todoTitle]);
}
return {
todoArray, setTodoArray,
completedTaskArray, setCompletedTaskArray,
addTodoItem,
addCompletedItem
}
}
export default useTodo;
CompletedTasklist(An example of my implementation of using a the context in one of it's children) -
import { useContext } from "react";
import {TodoContext, TodoProvider} from "../../Contexts/TodoContext"
const CompletedTaskList = () => {
const {
completedTaskArray
} = useContext(TodoContext);
return (
<div className="card todo-item">
<div className="card-header">
<h3> Completed Task</h3>
</div>
<div className="card-body">
<ul className="list-group ">
{completedTaskArray.map((item,index) => {
return <li className="list-group-item list-group-item-success" key={index}>{item}</li>
})}
</ul>
</div>
</div>
)
}
export default CompletedTaskList;
I've been trying to resolve this for a while now and cannot wrap my mind around it.
App.js
import React, { createContext, useContext } from 'react';
import CompletedTaskList from './comp';
import { TodoProvider } from './context';
function App() {
// you dont need useTodo, or TodoContext here
return (
<TodoProvider>
{/** todo Provider is a wrapper, you dont need to pass value as prop again, you are already doing it */}
<div className="App">
<div className="card" id="mainCard">
<div className="card-header" id="mainCardHeader">
<h4>Todo List</h4>
</div>
<CompletedTaskList />
</div>
</div>
</TodoProvider>
);
}
export default App;
Context
import React, { createContext } from 'react';
import useTodo from './useTodo';
// Define default values of your context data.
// otherwise everything would be undefined and you need to handle it everywhere
// you are using context
const TodoContext = createContext({
todoArray: [],
setTodoArray: () => {},
completedTaskArray: [],
addCompletedItem: () => {},
addTodoItem: () => {},
});
const TodoProvider = ({ children }) => {
const {
todoArray,
setTodoArray,
completedTaskArray,
addCompletedItem,
addTodoItem,
} = useTodo();
return (
<TodoContext.Provider
value={{
// <--- you were passing value incorrectly here, it should be an object
// you passed it as (......) instead of {...}
// curly braces not paranthesis
todoArray,
setTodoArray,
completedTaskArray,
addCompletedItem,
addTodoItem,
}}
>
{children}
</TodoContext.Provider>
);
};
export { TodoContext, TodoProvider };
Repasting the answer from the link i shared above.
I am having issues managing the state of my navbar using useContext. Atm my app renders the menu items as soon as the menu toggle. I want this event to happen only onClick, also the button does not log the console.log message, it works only when I click directly on the link item ex:home.
So I have 2 questions.
How do I manage my navbar state to show how to hide the menu items without having to create a new component for it?
How do I fix my click event for it be triggered on either the menu button itself or/and menu items?
Below you will code snippets for App.js, Layout.js, ThemeContext.js, useTheme.js, useToggle.js, ToggleContext.js and the Navbar where the toggle context is used.
I would really appreciate some help here guys, I am junior and really kind of stuck here.
Thanks in advance to you all.
App.js
//import { data } from '../../SkillData';
import Header from './Header';
import Navbar from './Navbar';
import Skills from './Skills';
import Layout from './Layout';
function App () {
return (
<Layout startingTheme="light" startingToggle={"show"}>
<div>
<Navbar />
<Header />
<Skills />
</div>
</Layout>
);
}
export default App;
Layout.js
import React, { useContext } from "react";
import { ThemeContext, ThemeProvider } from "../contexts/ThemeContext";
import { ToggleContext, ToggleProvider } from "../contexts/ToggleContext";
function Layout ({startingTheme, startingToggle, children}) {
return (
<>
<ThemeProvider startingTheme={startingTheme} >
<ToggleProvider startingToggle={startingToggle}>
<LayoutNoToggleProvider>
</LayoutNoToggleProvider>
</ToggleProvider>
<LayoutNoThemeProvider >{children}</LayoutNoThemeProvider>
</ThemeProvider>
</>
);
}
function LayoutNoToggleProvider ({children}) {
const toggle = useContext(ToggleContext);
return (
<div className={
toggle === false ? "navbar navbar-collapsed" : "navbar navbar-collapse show"
}>
{children}
</div>
)
}
function LayoutNoThemeProvider ({ children }) {
const {theme} = useContext(ThemeContext);
return (
<div className={
theme === "light" ?
"container-fluid bg-white" :
"container-fluid bg-dark"
}>
{children}
</div>
);
}
export default Layout;
ThemeContext
import React, { createContext} from "react";
import useTheme from "../hooks/useTheme";
export const ThemeContext = createContext();
function ThemeProvider ({children, startingTheme}) {
const { theme, setTheme } = useTheme(startingTheme);
return (
<ThemeContext.Provider value={
{theme, setTheme}
}>
{children}
</ThemeContext.Provider>
);
}
export { ThemeProvider };
useTheme.js
import { useState } from "react";
function useTheme (startingTheme ="light") {
const [theme, setTheme] = useState(startingTheme);
function validateTheme (themeValue) {
if (themeValue === "dark") {
setTheme("dark");
} else {
setTheme("light");
}
}
return {
theme,
setTheme: validateTheme,
}
}
export default useTheme;
ToggleContext.js
import React, { createContext } from "react";
import useToggle from "../hooks/useToggle";
export const ToggleContext = createContext();
function ToggleProvider({ children, startingToggle }) {
const { toggle, setToggle } = useToggle(startingToggle);
return (
<ToggleContext.Provider value={{ toggle, setToggle }}>
{children}
</ToggleContext.Provider>
);
}
export { ToggleProvider };
useToggle.js
import { useState } from "react";
function useToggle (startingToggle = false) {
const [toggle, setToggle] = useState(startingToggle);
function validateShowSidebar (showSidebarValue) {
if (showSidebarValue === "show") {
setToggle("show");
} else {
setToggle("");
}
}
return {
toggle,
setToggle: validateShowSidebar,
}
}
export default useToggle;
Navbar.js
import Image from "next/image";
import styles from "../../styles/Home.module.scss"
import Logo from "../../public/Knowledge Memo.svg"
import { useContext } from "react";
import { ThemeContext } from "../contexts/ThemeContext";
import { ToggleContext } from "../contexts/ToggleContext";
import Link from 'next/link';
import { useState } from "react";
const navbarData = [
{ id: "1",
title: "home",
ref: "#home"
},
{ id:"2",
title: "Skills",
ref: "#skills"
},
{ id:"3",
title: "The List",
ref: "#theList"
},
{ id: "4",
title: "Team",
ref: "#team"
},
{ id: "5",
title: "Contact",
ref: "#contact"
},
];
function Navbar() {
const theme = useContext(ThemeContext);
const toggle = useContext(ToggleContext);
return (
<>
<nav className={
theme === "light" ?
"navbar navbar-expand-lg navbar-dark fixed-top":
"navbar navbar-expand-lg navbar-dark bg-dark fixed-top id= mainNav"}>
<div className="container d-flex flex justify-content-between">
<a className="navbar-brand h-50" href="#page-top">
<div className="navbar-brand">
<Image
src={Logo}
alt="..."
fill="#fff"
objectFit="contain"
className="h-50"
/>
</div>
</a>
<button
onClick={ () => toggle === !toggle, console.log("clicked")}
className="navbar-toggler collapsed"
type="button"
data-bs-toggle="collapsed"
data-bs-target="#navbarResponsive"
aria-controls="navbarResponsive"
aria-expanded="false"
aria-label="Toggle navigation"
>
Menu
<i className="fa fa-bars ms-1 navbar-toggler" aria-hidden="true"></i>
</button>
{toggle ?
<div className="collapsed navbar-collapse mt-2 id=navbarResponsive">
<ul className="navbar-nav text-uppercase ms-auto py-4 py-lg-0">
{navbarData.map((link,idx) => {
return (
<li key={link.id}>
<Link href={`/${link.ref}`} className="nav-item" data-index={idx} passHref>
<a className="nav-link">
{link.title}
</a>
</Link>
</li>
);
})}
</ul>
</div>
: <div className="collapse navbar-collapse show mt-2 id=navbarResponsive">
<ul className="navbar-nav show text-uppercase ms-auto py-4 py-lg-0">
{navbarData.map((link,idx) => {
return (
<li key={link.id}>
<Link href={`/${link.ref}`} className="nav-item" data-index={idx} passHref>
<a className="nav-link">
{link.title}
</a>
</Link>
</li>
);
})}
</ul>
</div>}
</div>
</nav>
</>
);
}
export default Navbar;
You can try out this implemetation with reducers to handle for you the state change with localstorage. It is not an exact implemetation of your's but you can see the flow
In the AppContext.jsx
The AppContext holds the global state of the application so that it's easier working with a single context provider and dispatching actons to specific reducers to handle state change without providing many providers.
The combinedReducers handle reducer methods to a given state component
import { useReducer, createContext, useEffect } from "react";
import userReducer from "./reducers/userReducer";
import themeReducer from "./reducers/themeReducer";
export const APP_NAME = "test_app";
//Check the localstorage or set a default state
const initialState = JSON.parse(localStorage.getItem(APP_NAME))
? JSON.parse(localStorage.getItem(APP_NAME))
: {
user: {
username: "",
email: "",
isAdmin: false,
},
theme: { dark: false },
};
//Create your global context
const AppContext = createContext(initialState);
//Create combined reducers
const combinedReducers = ({ user, theme }, action) => ({
user: userReducer(user, action),
theme: themeReducer(theme, action),
});
const AppState = ({ children }) => {
//Making it to provider state
const [state, dispatch] = useReducer(combinedReducers, initialState);
useEffect(() => {
localStorage.setItem(APP_NAME, JSON.stringify(state));
}, [state]);
return (
<AppContext.Provider value={{ state, dispatch }}>
{children}
</AppContext.Provider>
);
};
export default AppState;
export { AppContext, AppState };
The above implementation works like redux but you destructure the given state to a specific reducer to handle the state change
In this I have used localstorage to keep a persistent state because with context API on page reload the state goes. Use the useEffect hook from react and add the state in the dependency array to ensure your state is in sync
In the UserReducer.jsx
const userReducer = (state, action) => {
const { type, payload } = action;
switch (type) {
case "LOGIN":
return { ...state, ...payload };
case "LOGOUT":
return {};
default:
return state;
}
};
export default userReducer;
In the ThemeReducer.jsx
const themeReducer = (state, action) => {
const { type, payload } = action;
switch (type) {
case "DARK":
return { ...payload };
default:
return state;
}
};
export default themeReducer;
Wrapping the whole app with a single provider in the index.jsx
import reactDom from "react-dom"
import React from "react"
import App from "./App"
import "./index.css"
import AppState from "./state/AppState"
reactDom.render(
<React.StrictMode>
<AppState >
<App />
</AppState>
</React.StrictMode>,
document.getElementById("root")
)
Accessing the context from App.jsx
import { useContext } from "react";
import { AppContext } from "./state/AppState";
const App = () => {
const { state, dispatch } = useContext(AppContext);
const handleLogin = () => {
dispatch({
type: "LOGIN",
payload: {
username: "Mike",
email: "mike#gmail.com",
isAdmin: false,
},
});
};
const handleLogout = () => {
dispatch({
type: "LOGOUT",
payload: {},
});
};
return (
<div className="main-container">
<div className="container">
<p>Username: {state.user.username ? state.user.username : "Unknown"}</p>
<p>Email: {state.user.email ? state.user.email : "Unknown"}</p>
</div>
<button onClick={handleLogin}>Login</button>
<button onClick={handleLogout} style={{ background: "red" }}>
Login
</button>
</div>
);
};
export default App;
Here is my code LINK if you want to see the structure Github
Note: please pull the repo to get access to the full App, there are too many files to simply share the code here. The repo: https://github.com/liviu-ganea/movie-database.git
So I've asked a question previously about my first React + Redux app and I've improved it. I got 2 problems now. The delete function for each movie (i.e. each entry in the redux state) is working just fine. So I now have problems with Login. Every time I press the button, nothing happens, and I suspect it's because the Login page component receives no props from the redux state.
My reasoning:
handleCLick = () => {
let userName = this.props.user.nickname;
let pw = this.props.user.password;
const nicknameBox = document.getElementById('nickname').textContent;
const passwordBox = document.getElementById('password').textContent.trim;
/*if (nicknameBox === userName && passwordBox === pw) {*/
this.props.loginUser((this.props.user.isLoggedIn = true));
this.props.history.push('/');
//}
};
When commented as it is now, it should go to the home page whenever I click the login button and if the password matches doesn't matter. Only there is no reaction to me pressing the button. Is the problem as I suspect?
And another problem: see in Movie.js I've tried to get the path (it's set in state: movies: cover) to the movie poster (located in ./src) so that when (or if) I make use of an API the path should set itself dynamically (i.e. I won't have to go into every component and add the path manually). Same thing on the Home page (./src/Components/Routes/Home.js)...no posters for any movie.
After Comments:
Component:
import React, { Component } from 'react';
import './routes.css';
import { loginAction } from '../../actions/userActions';
import { connect } from 'react-redux';
class Login extends Component {
handleCLick = () => {
let userName = this.props.user.nickname;
let pw = this.props.user.password;
const nicknameBox = document.getElementById('nickname').textContent;
const passwordBox = document.getElementById('password').textContent.trim;
/*if (nicknameBox === userName && passwordBox === pw) {*/
this.props.loginUser((this.props.user.isLoggedIn = true));
this.props.history.push('/');
//}
};
render() {
console.log(this.props);
return (
<div className="container page-container login-page">
<h3 className="page-title">Log In User</h3>
<div className="login-box">
<div>
<label className="login-labels" htmlFor="nickname">
Login{' '}
</label>
<input type="text" className="nickname-box" name="nickname" id="nickname" />
</div>
<div>
<label className="login-labels" htmlFor="password">
Password{' '}
</label>
<input type="password" className="password-box" name="password" id="pasword" />
</div>
<button className="login-button" onClick={this.handleClick}>
Login
</button>
</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
user: state.user,
};
};
const mapDispatchToProps = (dispatch) => {
return {
loginUser: (isLoggedIn) => {
dispatch(loginAction(isLoggedIn));
},
};
};
export default connect(mapDispatchToProps, mapStateToProps)(Login);
Reducer:
import { createStore } from 'redux';
const initialState = {
movies: [
{
id: '1',
title: 'John Wick Chapter 2',
year: '2017',
main_actor: 'Keanu Reeves',
summary: `The hitman that's been retired then back then retired strikes again, this time against the Mafia.`,
cover: '../john_wick_2.jpg',
},
{
id: '2',
title: 'Star Wars Revenge of the Sith',
year: '2005',
main_actor: 'Ewan McGregor',
summary: `Anakin betrays Space Jesus so General Kenobi is forced to Mustafar Anakin.`,
cover: '../sw_rots.png',
},
{
id: '3',
title: 'Star Wars The Clone Wars',
year: '2008 - 2020',
main_actor: 'Ewan McGregor, Hayden Christensen',
summary: `Yoda has finally overdosed on Ketamine, Mace Window hasn't been defenestrated yet and The Negotiator has proven himself incapable of falling to the Dark Side`,
cover: '../sw_tcw.jpg',
},
],
user: {
isLoggedIn: 'false',
nickname: 'obi_wan',
password: '12345',
},
};
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case 'DELETE_MOVIE':
let newMovieList = state.movies.filter((movie) => {
return action.id !== movie.id;
});
return {
...state,
movies: newMovieList,
};
case 'LOG_IN':
let newUserState = action.isLoggedIn;
return {
...state,
user: { ...state.user, isLoggedIn: action.payload.isLoggedIn },
};
default:
return state;
}
return state;
};
export default rootReducer;
userActions:
export const loginAction = (isLoggedIn) => {
return {
type: 'LOG_IN',
isLoggedIn: 'true',
};
};
index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import rootReducer from './reducers/rootReducer';
import App from './App';
const store = createStore(rootReducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'),
);
console.log(this.props) for the Login component:
[![enter image description here][1]][1]
Movie.js:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { deleteMovieAction } from '../actions/movieActions';
import './Routes/routes.css';
class Movie extends Component {
handleClick = () => {
this.props.deleteMovie(this.props.movie.id);
this.props.history.push('/');
};
render() {
console.log(this.props);
const isUser = this.props.user.isLoggedIn ? (
<button className="delete-movie-button" onClick={this.handleClick}>
Delete
</button>
) : null;
const theMovie = this.props.movie ? (
<div className="movie-container">
<img src={this.props.movie.cover} alt={this.props.movie.title} className="movie-cover" />
<div className="movie-container-content">
<h2 className="movie-title">{this.props.movie.title}</h2>
<p className="movie-description">{this.props.movie.summary}</p>
<div className="movie-data">
<p className="movie-year">{this.props.movie.year}</p>
<p className="movie-actor">{this.props.movie.main_actor}</p>
</div>
</div>
{isUser}
</div>
) : (
<div className="center">Getting data about the movie. Please wait.</div>
);
return <div className="container page-container">{theMovie}</div>;
}
}
const mapStateToProps = (state, ownProps) => {
let id = ownProps.match.params.movie_id;
return {
user: state.user,
movie: state.movies.find((movie) => movie.id === id),
};
};
const mapDispatchToProps = (dispatch) => {
return {
deleteMovie: (id) => {
dispatch(deleteMovieAction(id));
},
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Movie);
Movie.js - every movie component
import React, {Component} from 'react'
import { connect } from 'react-redux';
import { deleteMovieAction } from '../actions/movieActions'
import './Routes/routes.css'
class Movie extends Component {
handleClick = () => {
this.props.deleteMovie(this.props.movie.id);
this.props.history.push('/');
}
render () {
const coverino = ''
console.log(this.props);
const isUser = this.props.isLoggedIn ? ( <button className='delete-movie-button' onClick={this.handleClick}>Delete</button> ) : (null)
const theMovie = this.props.movie ? (
<div className='movie-container'>
<img src={this.props.movie.cover} alt={this.props.movie.title} className='movie-cover'/>
<div className='movie-container-content'>
<h2 className='movie-title'>{this.props.movie.title}</h2>
<p className='movie-description'>{this.props.movie.summary}</p>
<div className='movie-data'>
<p className='movie-year'>{this.props.movie.year}</p>
<p className='movie-actor'>{this.props.movie.main_actor}</p>
</div>
</div>
{isUser}
</div>
) : ( <div className="center">Getting data about the movie. Please wait.</div> );
return (
<div className='container page-container'>
{theMovie}
</div>
)
}
}
const mapStateToProps = (state, ownProps) => {
let id = ownProps.match.params.movie_id;
return {
isLoggedIn: state.user.isLoggedIn,
movie: state.movies.find(movie => movie.id === id),
}
}
const mapDispatchToProps = (dispatch) => {
return {
deleteMovie: (id) => { dispatch(deleteMovieAction(id))}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Movie)
The page display the entire movie list
import React, {Component} from 'react'
import { Link } from 'react-router-dom'
import { connect } from 'react-redux'
import './routes.css'
class Home extends Component{
render() {
console.log(this.props);
const { movies } = this.props;
const movieList = movies.length ? ( movies.map(movie => {
return (
<Link to={'/' + movie.id} key={movie.id}>
<div className='movie-container'>
<img src={require(`${movie.cover}`)} alt={movie.title} className='movie-cover'/>
<div className='movie-container-content'>
<h2 className='movie-title'>{movie.title}</h2>
<p className='movie-description'>{movie.summary}</p>
<div className='movie-data'>
<p className='movie-year'>{movie.year}</p>
<p className='movie-actor'>{movie.main_actor}</p>
</div>
</div>
</div>
</Link>
)
}
, )) : (<div className='waiting-for-movies'>Loading. Please wait</div>)
return (
<div className='container page-container'>
{movieList}
</div>
)
}
}
const mapStateToProps = (state) => {
return {
movies: state.movies
}
}
export default connect(mapStateToProps)(Home)
neither the require method, nor the simple {this.props...} works
[1]: https://i.stack.imgur.com/51FTy.png
I have been studying React for past few days. In my blog project, I am using Axios to get data from API. Here is my component:
import React, { useState, useEffect } from "react";
import axios from "axios";
import { apiConstants } from "../../constants";
import SinglePost from "./SinglePost";
const PostContent = props => {
const {
match: { params }
} = props;
const [post, setPost] = useState({});
useEffect(() => {
axios
.get(apiConstants.singlePost + `${params.post_slug}`)
.then(function(response) {
setPost(response.data);
})
.finally(function() {
// always executed
});
}, []);
return (
<React.Fragment>
<div className="container">
<div className="row">
<div className="col-lg-8 col-md-10 mx-auto">
<SinglePost post={post} />
</div>
</div>
</div>
</React.Fragment>
);
};
export default PostContent;
Above code works fine though I noticed the first time it tries to render the component with empty ({}) post object (Due to the default value in 'useState'). However, it causes issues in my child component cause it is directly using 'post' object properties. For example: 'post.content'. Here is my 'SinglePost' component's code:
const SinglePost = props => {
const { post } = props;
console.log(post);
return (
<div>{post.content}</div>
);
};
It returns undefined error for {post.content} object. To resolve the issue I had to use something like {post && <SinglePost post={post} />}, But it doesn't feel right. Is there any better way to handle such scenarios.
Consider revising the PostContent component's rendering logic to account for the case where no post data is present during the network request.
You could for instance initialise your post state to null, and then update the rendered result to prevent the SinglePost component from being rendered while post is null.
Once the network request is completed and the post state is set, the component will re-render causing SinglePost to be rendered with the non-null post state:
const PostContent = props => {
const {
match: { params }
} = props;
const [post, setPost] = useState(null); /* Update: "Initial data" set to null */
useEffect(() => {
axios
.get(apiConstants.singlePost + `${params.post_slug}`)
.then(function(response) {
setPost(response.data);
})
.finally(function() {
// always executed
});
}, []);
return (
{ /* <React.Fragment> Not needed */ }
<div className="container">
<div className="row">
<div className="col-lg-8 col-md-10 mx-auto">
{ /* Update: If post is null, assume network request is
still in progress so rendering loading message instead
of single post component */ }
{ post === null ? <p>Loading</p> : <SinglePost post={post} /> }
</div>
</div>
</div>
);
};
export default PostContent;
This approach is generally the simplest and more common pattern to async requests and rendering.
There are some other approaches that you might want to consider such as this declarative approach to data fetching, or the use of Reacts suspense feature for asynchronous rendering
You need to make post's initial value an array:
import React, { useState, useEffect } from "react";
import axios from "axios";
import { apiConstants } from "../../constants";
import SinglePost from "./SinglePost";
const PostContent = props => {
const {
match: { params }
} = props;
const [post, setPost] = useState([]);
useEffect(() => {
axios
.get(apiConstants.singlePost + `${params.post_slug}`)
.then(function(response) {
setPost(response.data);
})
.finally(function() {
// always executed
});
}, []);
return (
<React.Fragment>
<div className="container">
<div className="row">
<div className="col-lg-8 col-md-10 mx-auto">
<SinglePost post={post} />
</div>
</div>
</div>
</React.Fragment>
);
};
export default PostContent;
and in single post map through the array
const SinglePost = props => {
const { post } = props;
console.log(post);
return (
<div>
{post.map((post, key) => (
<div key={key}>{post.content}</div>
))}
</div>
);
};
You can do something like
import React, { useState, useEffect } from "react";
import axios from "axios";
import { apiConstants } from "../../constants";
import SinglePost from "./SinglePost";
const PostContent = props => {
const {
match: { params }
} = props;
const [posts, setPosts] = useState([]);
useEffect(() => {
axios
.get(apiConstants.singlePost + `${params.post_slug}`)
.then(function(response) {
setPosts(response.data);
})
.finally(function() {
// always executed
});
}, []);
return (
<React.Fragment>
<div className="container">
<div className="row">
<div className="col-lg-8 col-md-10 mx-auto">
{this.state.posts.map(post => (<SinglePost post={post} key={post.id} />))
</div>
</div>
</div>
</React.Fragment>
);
};
export default PostContent;